web-dev-qa-db-ja.com

onMeasureカスタムビューの説明

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

結局、私はいくつかの全く同じコンポーネントを使いたいのです。これらのコンポーネントを私のXMLファイルに追加しましたが、それらがどれだけの大きさになるべきかわかりません。後でその位置とサイズを設定したいのですが(描画時にonMeasureに設定する場合はonDrawにサイズを設定する必要がありますが、これもカスタムコンポーネントクラスで機能します)。まさに私がそれをする必要がある時?

300
sennin

onMeasure()は、カスタムビューを親から提供されるレイアウトの制約にどの程度依存させるかをAndroidに通知する機会です。これらのレイアウト制約が何であるかを学ぶのもカスタムビューの機会です(match_parent状況とwrap_content状況では異なる動作をしたい場合)。これらの制約は、メソッドに渡されるMeasureSpec値にまとめられています。これは、モード値の大まかな相関関係です。

  • EXACTは、layout_widthまたはlayout_heightの値が特定の値に設定されていたことを意味します。あなたはおそらくあなたの見解をこの大きさにするべきです。これはmatch_parentが使用されたときにトリガーされ、サイズを親ビューに正確に設定します(これはフレームワークのレイアウトに依存します)。
  • AT_MOSTは通常、layout_widthまたはlayout_heightの値がmatch_parentまたはwrap_contentに設定され、最大サイズが必要な場合(これはフレームワークのレイアウトによって異なります)、親ディメンションのサイズがその値です。あなたはこのサイズより大きくてはいけません。
  • UNSPECIFIEDは通常、layout_widthまたはlayout_heightの値が制限なしでwrap_contentに設定されたことを意味します。あなたはあなたが望むどんなサイズでもありえます。一部のレイアウトでは、このコールバックを使用して、2番目の測定要求で実際にどのスペックを通過させるかを決定する前に、希望のサイズを把握します。

onMeasure()との契約は、最後にsetMeasuredDimension()MUSTをビューのサイズに合わせて呼び出します。このメソッドは、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);
}

お役に立てば幸いです。

701
Devunwired

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

  • 正確に match_parentは正確に親のサイズ+サイズです
  • AT_MOST wrap_contentはAT_MOST MeasureSpecになります。
  • UNSPECIFIED決してトリガーされない

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

4
Florian

あなたが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()は常に100×100のサイズを設定する」と主張するこの 公式ドキュメントの部分 は間違っています。

0
David Refaeli