onMeasureカスタムビューの説明


316

カスタムコンポーネントを作ってみました。Viewクラスを拡張し、onDrawオーバーライドされたメソッドで描画を行います。なぜオーバーライドする必要があるのonMeasureですか?私がそうしなかった場合、すべてが正しいように見えました。誰かがそれを説明できますか?onMeasureメソッドをどのように記述する必要がありますか?私はいくつかのチュートリアルを見てきましたが、それぞれが他のものとは少し異なります。時々彼らsuper.onMeasureは最後に電話をかけます、時々彼らはsetMeasuredDimensionそれを使ってそれを呼ばなかった。違いはどこですか?

結局、まったく同じコンポーネントをいくつか使用したいと思っています。これらのコンポーネントをXMLファイルに追加しましたが、それらがどのくらいの大きさであるかわかりません。私は(私が設定したサイズに必要とする理由、後でその位置とサイズを設定したいonMeasureであればonDraw、私はそれを描くとき、としてうまく機能している)カスタムコンポーネントクラスに。正確にそれを行う必要があるのはいつですか?

回答:


735

onMeasure()カスタムビューを親から提供されるレイアウト制約に依存させる大きさをAndroidに伝える機会です。また、これらのレイアウト制約が何であるかを学習するカスタムビューの機会でもあります(あるmatch_parent状況で別の動作をしたい場合wrap_content)。これらの制約はMeasureSpec、メソッドに渡される値にパッケージ化されます。次に、モード値の大まかな相関関係を示します。

  • EXACTLYは、layout_widthor layout_height値が特定の値に設定されたことを意味します。あなたはおそらくあなたの見解をこのサイズにするべきです。これは、match_parentが使用されたときにトリガーされ、サイズを親ビューに正確に設定することもできます(これはフレームワークのレイアウトに依存します)。
  • AT_MOSTは、典型的には意味layout_widthまたはlayout_height値に設定されたmatch_parent、またはwrap_content最大サイズが必要とされる(これはレイアウトフレームワークに依存している)、親寸法の大きさの値です。このサイズより大きくないでください。
  • UNSPECIFIEDは通常、layout_widthor layout_height値がwrap_content制限なしに設定されたことを意味します。あなたが好きなサイズにすることができます。一部のレイアウトでは、このコールバックを使用して目的のサイズを把握してから、2番目のメジャーリクエストで実際にどのスペックを渡すかを決定します。

で存在する契約onMeasure()IS setMeasuredDimension() なければなりません。あなたはビューがなりたいサイズで最後に呼び出され。このメソッドは、にあるデフォルトの実装を含むすべてのフレームワーク実装によって呼び出されます。そのため、ユースケースに適合する場合は、代わりViewに呼び出す方が安全superです。

確かに、フレームワークはデフォルトの実装を適用しているため、このメソッドをオーバーライドする必要はないかもしれませんが、そうでない場合や、wrap_content両方の方向のカスタムビューでは、フレームワークがその大きさを認識していないため、ビューがまったく表示されない場合があります。

一般に、View別の既存のウィジェットではなくオーバーライドしている場合は、次のような単純な実装であっても、実装を提供することをお勧めします。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //MUST CALL THIS
    setMeasuredDimension(width, height);
}

お役に立てば幸いです。


1
@Devunwiredのちょっといい説明、これまで読んだ中で最高です。あなたの説明は私が持っていたいくつかの疑問に答え、いくつかの疑問をクリアしましたが、まだ残っています:カスタムビューが他のいくつかのビュー(どのタイプでもかまいません)と共にViewGroup内にある場合、ViewGroupはすべての子を取得しますLayoutParams制約のプローブごとに1つずつ、各子に制約に従って自己測定するように依頼しますか?
ファラオ2012

47
ViewGroupサブクラスのonMeasureをオーバーライドした場合、このコードは機能しないことに注意してください。サブビューは表示されず、サイズはすべて0x0になります。カスタムViewGroupのonMeasureをオーバーライドする必要がある場合は、widthMode、widthSize、heightMode、heightSizeを変更し、MeasureSpec.makeMeasureSpecを使用してそれらをmeasureSpecsにコンパイルし直し、結果​​の整数をsuper.onMeasureに渡します。
Alexey 2013

1
素晴らしい答え。Googleのドキュメントに従って、パディングを処理するのはビューの責任であることに注意してください。
jonstaff 2014年

4
Androidを扱いにくいレイアウトシステムにする複雑なc ** pについて。getParent()。get ***()...
Oliver Dixon

2
andとView呼ばれるクラスにはヘルパーメソッドがあり、 'if'句が行うことを実行する必要があります。特に、IFを頻繁に記述する必要がある場合に便利です。resolveSizeAndStateresolveSize
stan0 2017

5

実際には、値もラッピングコンテナーに依存するため、答えは完全ではありません。相対レイアウトまたは線形レイアウトの場合、値は次のように動作します。

  • 正確に match_parentは正確に+親のサイズ
  • AT_MOST wrap_contentはAT_MOST MeasureSpecになります
  • UNSPECIFIEDはトリガーされません

水平スクロールビューの場合、コードは機能します。


57
ここで回答が不完全だと思われる場合は、部分的な回答ではなく、追加してください。
ミハエル2013年

1
これをレイアウトの動作にリンクするための良いonyaですが、私の場合、カスタムビューでonMeasureが3回呼び出されます。問題のビューには、wrap_contentの高さと重み付けされた幅(幅= 0、重み= 1)がありました。最初の呼び出しにはUNSPECIFIED / UNSPECIFIEDがあり、2番目の呼び出しにはAT_MOST / EXACTLYがあり、3番目の呼び出しにはEXACTLY / EXACTLYがありました。
ウィリアムT.マラード2017

0

onMeasureを変更する必要がない場合は、オーバーライドする必要はまったくありません。

Devunwiredコード(ここで選択され、最も投票された回答)は、SDK実装がすでに行っているものとほぼ同じです(そして、私は確認しました-2009年以来それを行っていました)。

ここで onMeasureメソッドを確認できます

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

まったく同じコードで置き換えるSDKコードをオーバーライドしても意味がありません。

「デフォルトのonMeasure()は常に100x100のサイズを設定する」と主張するこの公式のドキュメントの部分は間違っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.