RelativeLayout 没有正确更新自定义视图的宽度
Posted
技术标签:
【中文标题】RelativeLayout 没有正确更新自定义视图的宽度【英文标题】:RelativeLayout not properly updating width of custom view 【发布时间】:2013-03-13 23:43:50 【问题描述】:我有这个容器类,用于制作其中一个维度(宽度或高度)与另一个维度的比率。例如,我想要一个宽度为“match_parent”的 16:9 布局容器。但是,当使用高度作为“match_parent”时,android 似乎无法正确重新布局自身。当我将高度设置为宽度的比率时,一切都很好!反之亦然,它不起作用。这是怎么回事?
public class RatioLayout extends ViewGroup
private float ratio = 1.6222f; // 4 x 3 default. 1.78 is 16 x 9
public float getRatio() return ratio;
public void setRatio(float ratio)
this.ratio = ratio;
requestLayout();
public RatioLayout(Context context)
this(context, null);
public RatioLayout(Context context, AttributeSet attrs)
this(context, attrs, 0);
public RatioLayout(Context context, AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
Log.i("Ratio", "/onMeasure");
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// int exactly = MeasureSpec.EXACTLY; // 1073741824
// int atMost = MeasureSpec.AT_MOST; // -2147483648
// int unspec = MeasureSpec.UNSPECIFIED; // 0
width = Math.round(height * ratio);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
int mw = getMeasuredWidth();
int mh = getMeasuredHeight();
Log.i("Ratio", "mw: " + mw + ", mh: " + mh);
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
Log.i("Ratio", "/onLayout");
Log.i("Ratio", "l: " + l + ", t: " + t + ", r:" + r + ", b:" + b);
Log.i("Ratio", "mw: " + getMeasuredWidth() + ", mh:" + getMeasuredHeight());
Log.i("Ratio", "w: " + getWidth() + ", mw:" + getHeight());
要查看它的实际效果,请使用如下布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
tools:context=".MainActivity" >
<com.example.RatioLayout
android:id="@+id/image"
android:layout_
android:layout_
android:layout_marginBottom="100dp"
android:layout_marginTop="100dp"
android:background="#FFFF00FF" />
</RelativeLayout>
这将是我的活动:
日志输出:
I/Ratio (9445): /onMeasure
I/Ratio (9445): mw: 1087, mh: 670
I/Ratio (9445): /onMeasure
I/Ratio (9445): mw: 655, mh: 404
I/Ratio (9445): /onLayout
I/Ratio (9445): l: 0, t: 133, r:1087, b:537
I/Ratio (9445): mw: 655, mh:404
I/Ratio (9445): w: 1087, mw:404 <--- NO reason this should not be 655
为什么 Android 不支持我最新的测量宽度,而是使用旧版本但使用最新高度?
编辑:已更新。使 RatioLayout NOT 的父级成为 RelativeLayout 但 LinearLayout 或 FrameLayout 给我正确的行为。出于某种原因,RelativeLayout 正在“缓存”measuredWidth 而不是使用最新的。
编辑 2:RelativeLayout.onLayout 中的这条评论似乎证实了我的“它正在缓存”,我认为这是一个错误
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
int count = getChildCount();
// TODO: we need to find another way to implement RelativeLayout
// This implementation cannot handle every case
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
最终编辑好的。我放弃。这是RelativeLayout 中的一个合法错误。这种代码修复了它,但它会产生与 toRightOf 属性有关的问题。我发现的解决方法是将这个 RatioLayout 嵌套到另一个 ViewGroup 中,比如 LinerLayout。为好奇的人准备的代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
Log.i("Ratio", "/onMeasure");
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// int exactly = MeasureSpec.EXACTLY; // 1073741824
// int atMost = MeasureSpec.AT_MOST; // -2147483648
// int unspec = MeasureSpec.UNSPECIFIED; // 0
width = Math.round(height * ratio);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
if (getParent() instanceof RelativeLayout)
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)getLayoutParams();
Class<?> clazz = RelativeLayout.LayoutParams.class;
try
Field left = clazz.getDeclaredField("mLeft");
Field right = clazz.getDeclaredField("mRight");
left.setAccessible(true);
right.setAccessible(true);
int l = left.getInt(params);
if (l == -1) l = params.leftMargin; // if the value is uninitialized, set it to 0;
if (l == -1) l = 0; // if the value is uninitialized, set it to 0;
// setting this seems to break the layout_marginLeft properties.
right.setInt(params, l + getMeasuredWidth());
catch (NoSuchFieldException e)
Log.e("Ration", "error", e);
catch (IllegalArgumentException e)
Log.e("Ration", "error", e);
catch (IllegalAccessException e)
Log.e("Ration", "error", e);
int mw = getMeasuredWidth();
int mh = getMeasuredHeight();
lastWidth = mw;
Log.i("Ratio", "mw: " + mw + ", mh: " + mh);
【问题讨论】:
【参考方案1】:我实际上已经尝试过完全按照您之前尝试的方式进行操作,也就是说,使View
尽可能大,同时保持一定的纵横比(即正方形或 4:3 或类似的东西) .
我的问题是,当我的视图位于 ScrollView
时,尺寸计算不正确。我无法弄清楚这一点,但我会在下面发布我的代码以防万一。它与您的代码非常相似,但我继承自 FrameLayout
并最终调用 super.onMeasure()
。
我仔细检查了我在其中使用它的项目,我确实将它作为 RelativeLayout
的直接子代。
Java:
/**
* A FrameLayout which tries to be as big as possible while maintaining a given ratio between its width and height.
* Formula: Height = Width / ratio;
* Usage:
* 1) Set layout_width and layout_height to "match_parent"
* 2) For 4:3 for example, set ratio to 4/3 = 1.333333 etc. Or don't specify, and it will be square by default.
*/
public class RatioFrameLayout extends FrameLayout
private final static float DEFAULT_RATIO = 1f;
private float ratio;
private boolean measured = false;
public RatioFrameLayout(Context context)
super(context);
public RatioFrameLayout(Context context, AttributeSet attrs)
super(context, attrs);
readAttributes(context, attrs);
public RatioFrameLayout(Context context, AttributeSet attrs, int defStyle)
super(context, attrs, defStyle);
readAttributes(context, attrs);
private void readAttributes(Context context, AttributeSet attrs)
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatioFrameLayout);
String value = a.getString(R.styleable.RatioFrameLayout_ratio);
try
ratio = Float.parseFloat(value);
catch (Exception e)
ratio = DEFAULT_RATIO;
a.recycle();
public float getRatio()
return ratio;
public void setRatio(float ratio)
this.ratio = ratio;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int targetWidth = getMeasuredWidth();
int targetHeight = Math.round((float)getMeasuredWidth() / ratio);
if (targetHeight > getMeasuredHeight() && getMeasuredHeight() != 0)
targetWidth = Math.round(getMeasuredHeight() * ratio);
targetHeight = getMeasuredHeight();
super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(targetHeight, MeasureSpec.EXACTLY));
private void printMeasureSpec(String description, int value)
int mode = MeasureSpec.getMode(value);
String modeName = mode == MeasureSpec.AT_MOST ? "AT_MOST"
: mode == MeasureSpec.EXACTLY ? "EXACTLY" : "UNSPECIFIED";
DLog.d(String.format("Measure spec for %s, mode = %s, size = %d", description, modeName, MeasureSpec.getSize(value)));
attrs.xml
:
<resources>
<declare-styleable name="RatioFrameLayout">
<attr name="ratio" format="string|reference" />
</declare-styleable>
</resources>
【讨论】:
以上是关于RelativeLayout 没有正确更新自定义视图的宽度的主要内容,如果未能解决你的问题,请参考以下文章
如何从持久存储中获取数据并显示在自定义表格视单元上?有没有示例代码?