我的问题之前的版本太罗嗦了。人们无法理解它,所以下面是一个完整的重写。如果您对旧版本感兴趣,请参阅 edit history。
RelativeLayout
的父级将 MeasureSpec
s 发送到其 subview 的 onMeasure
方法,以查看 child 想要多大。这发生在几个 channel 中。
我的自定义 View
我有一个 custom view 。随着 View 内容的增加, View 的高度也会增加。当 View 达到父级允许的最大高度时,任何其他内容的 View 宽度都会增加(只要为宽度选择了 wrap_content
)。因此,自定义 View 的宽度直接取决于父级所说的最大高度。
(不和谐的)父子对话
onMeasure
传递 1
RelativeLayout
父级告诉我的观点,“你可以是任何宽度 up to 900
和任何高度 up to 600
。你说什么?”
我的观点是,“嗯,在那个高度,我可以容纳宽度为 100
的所有东西。所以我将采用 100
的宽度和 600
的高度。”
onMeasure
第 2 遍
RelativeLayout
父级告诉我的观点,“你上次告诉我你想要一个 100
的宽度,所以让我们将其设置为 exact 宽度。现在,基于这个宽度,你想要什么样的高度?任何 up to 500
没问题。”
“嘿!”我的看法答复。 “如果你只给我一个 500
的最大高度,那么 100
太窄了。我需要一个 200
的宽度来达到那个高度。但是很好,随你的便。我不会违反规则(还...)。我将采用 100
的宽度和 500
的高度。"
最终结果
RelativeLayout
父级为 View 分配最终大小,宽度为 100
,高度为 500
。这对于 View 来说当然太窄了,并且部分内容被剪裁了。
“叹息,”我的看法是。 “为什么我的 parent 不让我变宽?空间很大。也许 Stack Overflow 上的人可以给我一些建议。”
最佳答案
更新:修改代码以修复一些问题。
首先,让我说您提出了一个很好的问题并且很好地提出了问题(两次!)这是我的解决方案:
onMeasure
似乎发生了很多事情,从表面上看,这并没有多大意义。既然如此,我们就让 onMeasure
照常运行,最后通过 onLayout
判断 View
的边界将 mStickyWidth
设置为我们将接受的新最小宽度。在 onPreDraw
中,使用 ViewTreeObserver.OnPreDrawListener
,我们将强制另一个布局 (requestLayout
)。来自 documentation (强调):
boolean onPreDraw ()
Callback method to be invoked when the view tree is about to be drawn. At this point, all views in the tree have been measured and given a frame. Clients can use this to adjust their scroll bounds or even to request a new layout before drawing occurs.
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;
}
关于android - 自定义 View 的 onMeasure : how to get width based on height,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42390378/