私の質問の以前のバージョンは冗長すぎました。人々はそれを理解できなかったので、以下は完全な書き直しです。古いバージョンに興味がある場合は 編集履歴 を参照してください。
RelativeLayout
親は、子の大きさを確認するために、子ビューのMeasureSpec
メソッドに onMeasure
s を送信します。これは複数のパスで発生します。
カスタムビュー があります。ビューのコンテンツが増加すると、ビューの高さが増加します。ビューが親が許可する最大の高さに達すると、ビューの幅が追加コンテンツに対して増加します(幅にwrap_content
が選択されている限り)。したがって、カスタムビューの幅は、最大の高さが必要であると親が言う内容に直接依存します。
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の誰かが私にアドバイスをしてくれるでしょう。」
更新:いくつかを修正するためにコードを修正しました
まず、あなたが素晴らしい質問をし、問題を非常にうまく説明したとしましょう(2倍!)これが私の解決策です:
onMeasure
には、表面上はあまり意味のないことがたくさんあるようです。そのため、onMeasure
をそのまま実行し、最後にView
をonLayout
に設定して、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;
}
_
カスタムレイアウトを作成するには、この記事を読んで理解する必要があります https://developer.Android.com/guide/topics/ui/how-Android-draws.html
希望する動作を実装することは難しくありません。カスタムビューでonMeasureとonLayoutをオーバーライドするだけです。
OnMeasureでは、カスタムビューの可能な最大の高さを取得し、サイクルの子に対してmeasure()を呼び出します。子の測定後、各子から必要な高さを取得し、現在の列に子が収まるかどうかを計算します。新しい列のカスタムビューの幅を広げない場合は計算します。
OnLayoutでは、すべての子ビューのlayout()を呼び出して、親内での位置を設定する必要があります。この位置は、前にonMeasureで計算したものです。