我的问题的上一个版本过于冗长。人们无法理解它,所以以下是完全重写。如果您对旧版本感兴趣,请参阅edit history。
RelativeLayout
父母将MeasureSpec
s发送到其子视图的onMeasure
方法,以查看孩子想要的大小。这发生在几次通过中。
我有custom view。随着视图内容的增加,视图的高度也会增加。当视图达到父级允许的最大高度时,视图的宽度会随着任何其他内容而增加(只要为宽度选择了wrap_content
)。因此,自定义视图的宽度直接取决于父级所说的最大高度必须是什么。
onMeasure
传递1
RelativeLayout
父母告诉我的观点,"您可以是任意宽度up to 900
和任何身高up to 600
。你怎么说?"
我的观点是,"嗯,在那个高度,我可以适应宽度为100
的所有东西。所以我的宽度为100
,高度为600
。"
onMeasure
传递2
RelativeLayout
父母告诉我的观点,"您上次告诉我您希望宽度为100
,所以让我们将其设为exact宽度。现在,基于那个宽度,你想要什么样的高度?任何up to 500
都可以。"
"嘿&#34!;我的观点回复。 "如果你只给我一个500
的最大高度,那么100
就太窄了。我需要宽度为200
的高度。但很好,按你自己的方式行事。我不会违反规则(还有......)。我的宽度为100
,高度为500
。"
最终结果
RelativeLayout
父级为视图指定宽度为100
的最终尺寸,为高度指定500
。对于视图来说,这当然太窄了,部分内容会被剪裁。
"叹息,"认为我的看法。 "为什么我的父母会让我变宽?有足够的空间。也许Stack Overflow上的某个人可以给我一些建议。"
答案 0 :(得分:9)
更新:修改代码以修复某些问题。
首先,我要说你提出了一个很好的问题并且很好地解决了问题(两次!)这是我的解决方案:
从onMeasure
开始,似乎有很多事情表面上没有多大意义。既然如此,我们会让onMeasure
按原样运行,最后通过将View
设置为onLayout
来判断mStickyWidth
的{{1}}界限我们接受的最小宽度。在onPreDraw
中,使用ViewTreeObserver.OnPreDrawListener
,我们将强制执行另一种布局(requestLayout
)。从documentation(强调添加):
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;
}
答案 1 :(得分:1)
要进行自定义布局,您需要阅读并理解本文https://developer.android.com/guide/topics/ui/how-android-draws.html
实现您想要的行为并不困难。您只需在自定义视图中覆盖onMeasure和onLayout。
在onMeasure中,您将获得自定义视图的最大可能高度,并为周期中的子项调用measure()。儿童测量后从每个孩子获得所需的身高并计算出是否适合当前列,如果不为新列增加自定义视图范围。
在onLayout中,您必须为所有子视图调用layout()以在父级中设置它们的位置。您之前在onMeasure中计算过的这个位置。