android - RelativeLayout 未正确更新自定义 View 的宽度

标签 android android-layout

我有一个容器类,用于使其中一个维度(宽度或高度)与另一个维度成比例。例如,我想要一个宽度为“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_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<com.example.RatioLayout
    android:id="@+id/image"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_marginBottom="100dp"
    android:layout_marginTop="100dp"
    android:background="#FFFF00FF" />

</RelativeLayout>

这将是我的 Activity :

日志输出:

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 不支持我最新的 measuredWidth 而使用旧版本但使用最新的高度?

编辑: 已更新。让 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);
}

最佳答案

我实际上已经尝试完全按照您之前的尝试去做,也就是说,使 View 尽可能大,同时保持一定的宽高比(即正方形或 4:3或类似的东西)。

我的问题是,当我的 View 在 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>

关于android - RelativeLayout 未正确更新自定义 View 的宽度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15398643/

相关文章:

android - 即使我完成了该应用程序,它也会继续返回到之前的 Activity

android - 9-patch 拉伸(stretch),当它不应该拉伸(stretch)时,Android

java - 如何更改向上按钮 Android ActionBar?

android - wifi 断开后,我的重复闹钟不起作用

android - 在线性布局中将焦点从父级传递给子级

安卓。在不推开屏幕的情况下对齐线性布局内的 View 。

android - 如何调整框架布局的权重?

android - Android P 预览版 toast View 的 IllegalStateException

android - Android Studio 中 XML 布局的意外自动格式化

android - 是否可以为我的 Android 应用程序提供自定义尺寸?