web-dev-qa-db-ja.com

カスタムビューonMeasure:高さに基づいて幅を取得する方法

私の質問の以前のバージョンは冗長すぎました。人々はそれを理解できなかったので、以下は完全な書き直しです。古いバージョンに興味がある場合は 編集履歴 を参照してください。

RelativeLayout親は、子の大きさを確認するために、子ビューのMeasureSpecメソッドに onMeasures を送信します。これは複数のパスで発生します。

私のカスタムビュー

カスタムビュー があります。ビューのコンテンツが増加すると、ビューの高さが増加します。ビューが親が許可する最大の高さに達すると、ビューの幅が追加コンテンツに対して増加します(幅にwrap_contentが選択されている限り)。したがって、カスタムビューの幅は、最大の高さが必要であると親が言う内容に直接依存します。

enter image description here

(不調和な)親子の会話

onMeasure pass 1

親であるRelativeLayoutは、「任意の幅 最大900にすることができ、任意の高さ 最大600にすることができます。あなたは何を言っていますか?"

私の見解では、「まあ、その高さでは、100の幅ですべてを合わせることができます。そのため、100の幅と600の高さを取ります。」

onMeasure pass 2

RelativeLayout親は私の見解を伝えます、「あなたは前回、100の幅が欲しいと言ったので、それを exact 幅として設定しましょう。その幅、どのような高さをご希望ですか?何でも 最大500で問題ありません。」

「ねえ!」私の見解は答えます。 「最大高さ500のみを指定している場合、100は狭すぎます。その高さには200の幅が必要です。しかし、問題ありません。ルールを破らない (まだ...)。幅は100、高さは500とします。 "

最終結果

RelativeLayoutは、ビューに幅として100、高さとして500の最終サイズを割り当てます。これはもちろんビューには狭すぎて、コンテンツの一部が切り取られます。

「ため息」と私の考えは思います。 「どうして私の親は私に幅を広くさせないのですか?十分な余地があります。多分、Stack Overflowの誰かが私にアドバイスをしてくれるでしょう。」

17
Suragch

更新:いくつかを修正するためにコードを修正しました

まず、あなたが素晴らしい質問をし、問題を非常にうまく説明したとしましょう(2倍!)これが私の解決策です:

onMeasureには、表面上はあまり意味のないことがたくさんあるようです。そのため、onMeasureをそのまま実行し、最後にViewonLayoutに設定して、mStickyWidthの境界の判定をパスします。受け入れる新しい最小幅。 onPreDrawでは、_ViewTreeObserver.OnPreDrawListener_を使用して、別のレイアウト(requestLayout)を強制します。 ドキュメント (強調を追加)から:

boolean onPreDraw ()

ビューツリーが描画されようとしているときに呼び出されるコールバックメソッド。この時点で、ツリー内のすべてのビューが測定され、フレームが与えられています。クライアントはこれを使用してスクロール境界を調整したり、描画が発生する前に新しいレイアウトを要求したりできます

onLayoutに設定された新しい最小幅はonMeasureによって強制されるようになり、可能なことについてよりスマートになりました。

私はあなたのサンプルコードでこれをテストしました、そしてそれはうまくいくようです。さらに多くのテストが必要になります。これを行うには他の方法があるかもしれませんが、それがアプローチの要点です。

CustomView.Java

_import Android.content.Context;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.view.View;
import Android.view.ViewTreeObserver;

public class CustomView extends View
        implements ViewTreeObserver.OnPreDrawListener {
    private int mStickyWidth = STICKY_WIDTH_UNDEFINED;

    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        logMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
        int desiredHeight = 10000; // some value that is too high for the screen
        int desiredWidth;

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

        int width;
        int height;

        // Height
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(desiredHeight, heightSize);
        } else {
            height = desiredHeight;
        }

        // Width
        if (mStickyWidth != STICKY_WIDTH_UNDEFINED) {
            // This is the second time through layout and we are trying renogitiate a greater
            // width (mStickyWidth) without breaking the contract with the View.
            desiredWidth = mStickyWidth;
        } else if (height > BREAK_HEIGHT) { // a number between onMeasure's two final height requirements
            desiredWidth = ARBITRARY_WIDTH_LESSER; // arbitrary number
        } else {
            desiredWidth = ARBITRARY_WIDTH_GREATER; // arbitrary number
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }

        Log.d(TAG, "setMeasuredDimension(" + width + ", " + height + ")");
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        int w = right - left;
        int h = bottom - top;

        super.onLayout(changed, left, top, right, bottom);
        // Here we need to determine if the width has been unnecessarily constrained.
        // We will try for a re-fit only once. If the sticky width is defined, we have
        // already tried to re-fit once, so we are not going to have another go at it since it
        // will (probably) have the same result.
        if (h <= BREAK_HEIGHT && (w < ARBITRARY_WIDTH_GREATER)
                && (mStickyWidth == STICKY_WIDTH_UNDEFINED)) {
            mStickyWidth = ARBITRARY_WIDTH_GREATER;
            getViewTreeObserver().addOnPreDrawListener(this);
        } else {
            mStickyWidth = STICKY_WIDTH_UNDEFINED;
        }
        Log.d(TAG, ">>>>onLayout: w=" + w + " h=" + h + " mStickyWidth=" + mStickyWidth);
    }

    @Override
    public boolean onPreDraw() {
        getViewTreeObserver().removeOnPreDrawListener(this);
        if (mStickyWidth == STICKY_WIDTH_UNDEFINED) { // Happy with the selected width.
            return true;
        }

        Log.d(TAG, ">>>>onPreDraw() requesting new layout");
        requestLayout();
        return false;
    }

    protected void logMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        String measureSpecHeight;
        String measureSpecWidth;

        if (heightMode == MeasureSpec.EXACTLY) {
            measureSpecHeight = "EXACTLY";
        } else if (heightMode == MeasureSpec.AT_MOST) {
            measureSpecHeight = "AT_MOST";
        } else {
            measureSpecHeight = "UNSPECIFIED";
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            measureSpecWidth = "EXACTLY";
        } else if (widthMode == MeasureSpec.AT_MOST) {
            measureSpecWidth = "AT_MOST";
        } else {
            measureSpecWidth = "UNSPECIFIED";
        }

        Log.d(TAG, "Width: " + measureSpecWidth + ", " + widthSize + " Height: "
                + measureSpecHeight + ", " + heightSize);
    }

    private static final String TAG = "CustomView";
    private static final int STICKY_WIDTH_UNDEFINED = -1;
    private static final int BREAK_HEIGHT = 1950;
    private static final int ARBITRARY_WIDTH_LESSER = 200;
    private static final int ARBITRARY_WIDTH_GREATER = 800;
}
_
9
Cheticamp

カスタムレイアウトを作成するには、この記事を読んで理解する必要があります https://developer.Android.com/guide/topics/ui/how-Android-draws.html

希望する動作を実装することは難しくありません。カスタムビューでonMeasureとonLayoutをオーバーライドするだけです。

OnMeasureでは、カスタムビューの可能な最大の高さを取得し、サイクルの子に対してmeasure()を呼び出します。子の測定後、各子から必要な高さを取得し、現在の列に子が収まるかどうかを計算します。新しい列のカスタムビューの幅を広げない場合は計算します。

OnLayoutでは、すべての子ビューのlayout()を呼び出して、親内での位置を設定する必要があります。この位置は、前にonMeasureで計算したものです。

1
Nik