RelativeLayout的无法正常更新的宽度自定义视图 [英] RelativeLayout not properly updating width of custom view

查看:520
本文介绍了RelativeLayout的无法正常更新的宽度自定义视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有用于使其中一个维度这个容器类,宽度或高度,另一个尺寸的比率。例如,我要一个16:9的布局容器,宽度为match_parent。但是,使用高度match_parent时,机器人似乎并没有正确地重新布局本身。当我设置的高度是宽度的一切的比率是好的!反之亦然,这是行不通的。这是怎么回事?

 公共类RatioLayout扩展的ViewGroup {

私人浮动率= 1.6222f; // 4×3的默认。 1.78是16×9
公众持股量getRatio(){回报率; }
公共无效setRatio(浮动比率){
    this.ratio =比;
    requestLayout();
}

公共RatioLayout(上下文的背景下){
    这(背景下,NULL);
}

公共RatioLayout(上下文的背景下,ATTRS的AttributeSet){
    这(背景下,ATTRS,0);
}

公共RatioLayout(上下文的背景下,ATTRS的AttributeSet,诠释defStyle){
    超(背景下,ATTRS,defStyle);
}

@覆盖
保护无效onMeasure(INT widthMeasureSpec,诠释heightMeasureSpec){
    Log.i(比率,/ onMeasure);
    INT宽度= MeasureSpec.getSize(widthMeasureSpec);
    INT高= MeasureSpec.getSize(heightMeasureSpec);
    INT widthMode = MeasureSpec.getMode(widthMeasureSpec);
    INT heightMode = MeasureSpec.getMode(heightMeasureSpec);

// INT正好= MeasureSpec.EXACTLY; // 1073741824
// INT atMost = MeasureSpec.AT_MOST; // -2147483648
// INT UNSPEC = MeasureSpec.UNSPECIFIED; // 0

    宽度= Math.round(高*比);
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(宽,MeasureSpec.EXACTLY);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(高度,MeasureSpec.EXACTLY);

    setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);

    INT MW = getMeasuredWidth();
    INT MH = getMeasuredHeight();
    Log.i(比率,MW:+ MW +,MH:+ MH);
}


@覆盖
保护无效onLayout(布尔改变,诠释L,INT T,INT R,int b)在{
    Log.i(比率,/ onLayout);
    Log.i(比率,升+ 1 +,厚度:+ T +,R:+ R +,B:+ b)的;
    Log.i(比率,MW:+ getMeasuredWidth()+,MH:+ getMeasuredHeight());
    Log.i(比率,瓦特:+的getWidth()+,MW:+的getHeight());
}

}
 

要看到它在行动中,使用的布局是这样的:

 < RelativeLayout的的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
的xmlns:工具=htt​​p://schemas.android.com/tool​​s
机器人:layout_width =match_parent
机器人:layout_height =match_parent
工具:上下文=MainActivity。>

< com.example.RatioLayout
    机器人:ID =@ + ID /图像
    机器人:layout_width =0dp
    机器人:layout_height =match_parent
    机器人:layout_marginBottom =100dp
    机器人:layout_marginTop =100dp
    机器人:后台=#FFFF00FF/>

< / RelativeLayout的>
 

这将是我的活动:

日志输出:

  I /比(9445):/ onMeasure
I /比(9445):分子量:1087,MH:670
I /比(9445):/ onMeasure
I /比(9445):MW:655,MH:404
I /比(9445):/ onLayout
I /比(9445):L:0,T:133,R:1087,B:537
I /比(9445):MW:655,MH:404
I /比(9445):W:1087,MW:404< ---没有原因,这不应该是655
 

为什么Android的不尊敬我的最新measuredWidth,但使用的是旧版本,但最新的高度?

编辑:更新。使RatioLayout的父不会 RelativeLayout的,但一个LinearLayout中或的FrameLayout给了我正确的行为。出于某种原因,RelativeLayout的是高速缓存measuredWidth和没有使用最新的。

编辑2:在RelativeLayout.onLayout这一评论似乎证实了我这是高速缓存,我相信这是一个错误

  @覆盖
保护无效onLayout(布尔改变,诠释L,INT T,INT R,int b)在{
    //布局实际上已经被执行及职位
    //缓存。应用缓存值给孩子们。
    诠释计数= getChildCount();

// TODO:我们需要找到另一种方式来实现RelativeLayout的
//这个实现无法处理每一个案件
@覆盖
保护无效onMeasure(INT widthMeasureSpec,诠释heightMeasureSpec){
 

最后编辑确定。我放弃。这是RelativeLayout的一个合法的错误。这code类修复它,但它造成的toRightOf性能问题。周围的工作中,我发现了窝这个RatioLayout到像LinerLayout另一个ViewGroup中。 code对于那些好奇

  @覆盖
保护无效onMeasure(INT widthMeasureSpec,诠释heightMeasureSpec){
    Log.i(比率,/ onMeasure);
    INT宽度= MeasureSpec.getSize(widthMeasureSpec);
    INT高= MeasureSpec.getSize(heightMeasureSpec);
    INT widthMode = MeasureSpec.getMode(widthMeasureSpec);
    INT heightMode = MeasureSpec.getMode(heightMeasureSpec);

// INT正好= MeasureSpec.EXACTLY; // 1073741824
// INT atMost = MeasureSpec.AT_MOST; // -2147483648
// INT UNSPEC = MeasureSpec.UNSPECIFIED; // 0

    宽度= Math.round(高*比);
    widthMeasureSpec = MeasureSpec.makeMeasureSpec(宽,MeasureSpec.EXACTLY);
    heightMeasureSpec = MeasureSpec.makeMeasureSpec(高度,MeasureSpec.EXACTLY);

    setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);

    如果(的getParent()的instanceof RelativeLayout的){
        RelativeLayout.LayoutParams PARAMS =(RelativeLayout.LayoutParams)getLayoutParams();
        类<> clazz所= RelativeLayout.LayoutParams.class;
        尝试 {
            现场留下= clazz.getDeclaredField(MLEFT);
            场右侧= clazz.getDeclaredField(mRight);
            left.setAccessible(真正的);
            right.setAccessible(真正的);
            INT L = left.getInt(PARAMS);
            如果(L == -1)L = params.leftMargin; //如果该值未初始化,将其设置为0;
            如果(升== -1)升= 0; //如果该值未初始化,将其设置为0;
            //设置这似乎打破layout_marginLeft属性。
            right.setInt(参数,可以升+ getMeasuredWidth());
        }赶上(NoSuchFieldException E){
            Log.e(理性,错误,E);
        }赶上(抛出:IllegalArgumentException E){
            Log.e(理性,错误,E);
        }赶上(IllegalAccessException E){
            Log.e(理性,错误,E);
        }
    }

    INT MW = getMeasuredWidth();
    INT MH = getMeasuredHeight();
    lastWidth =兆瓦;
    Log.i(比率,MW:+ MW +,MH:+ MH);
}
 

解决方案

其实我已经尝试做你正在尝试做之前,也就是,让完全是一个查看要尽可能大,同时保持某些方面比(即方形或4:3或类似的东西)。

我的问题是,当我的观点是在一个滚动型尺寸得到了计算错误。我无法弄清楚这一个,但我会后我的code以下,以防万一它帮助。这真的很相似,你的code,但我从的FrameLayout 继承和我最终调用 super.onMeasure()

我仔细检查我就是用这个项目,我也确实有它的直接子 RelativeLayout的

Java的:

  / **
 *一个的FrameLayout它试图在保持其宽度和高度之间的给定的比例要尽可能大。
 *公式:高度=宽度/比率;
 * 用法:
 * 1)设置layout_width和layout_height为match_parent
 * 2)对于4:3比如,设置比例为4/3 = 1.333333等。或者不指定,这将是默认广场。
 * /
公共类RatioFrameLayout扩展的FrameLayout
{
    私人最终静态浮动DEFAULT_RATIO = 1F;

    私人流通股比例;
    测量= FALSE私人布尔;

    公共RatioFrameLayout(上下文的背景下)
    {
        超(上下文);
    }

    公共RatioFrameLayout(上下文的背景下,ATTRS的AttributeSet)
    {
        超(背景下,ATTRS);
        readAttributes(背景下,ATTRS);
    }

    公共RatioFrameLayout(上下文的背景下,ATTRS的AttributeSet,INT defStyle)
    {
        超(背景下,ATTRS,defStyle);
        readAttributes(背景下,ATTRS);
    }

    私人无效readAttributes(上下文的背景下,ATTRS的AttributeSet)
    {
        TypedArray A = context.obtainStyledAttributes(ATTRS,R.styleable.RatioFrameLayout);

        字符串值= a.getString(R.styleable.RatioFrameLayout_ratio);
        尝试
        {
            比= Float.parseFloat(值);
        }
        赶上(例外五)
        {
            比= DEFAULT_RATIO;
        }

        a.recycle();
    }

    公众持股量getRatio()
    {
        回报率;
    }

    公共无效setRatio(浮动比率)
    {
        this.ratio =比;
    }

    @覆盖
    保护无效onMeasure(INT widthMeasureSpec,INT heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);

        INT targetWidth = getMeasuredWidth();
        INT targetHeight = Math.round((浮点)getMeasuredWidth()/比例);

        如果(targetHeight> getMeasuredHeight()&安培;&安培;!getMeasuredHeight()= 0)
        {
            targetWidth = Math.round(getMeasuredHeight()*比率);
            targetHeight = getMeasuredHeight();
        }

        super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth,MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(targetHeight,MeasureSpec.EXACTLY));
    }

    私人无效printMeasureSpec(字符串描述,int值)
    {
        INT模式= MeasureSpec.getMode(值);
        字符串模式名=模式== MeasureSpec.AT_MOST? 最多
                :模式== MeasureSpec.EXACTLY? 正是:未指定;
        DLog.d(的String.Format(测量规格%s的,模式=%S,大小=%D,描述,模式名,MeasureSpec.getSize(值)));
    }
}
 

attrs.xml

 <资源>
    <申报,设置样式名称=RatioFrameLayout>
        < attr指示NAME =比FORMAT =字符串|引用/>
    < /申报,设置样式>
< /资源>
 

I have this container class that is used to make one of the dimensions, either the width or height, a ratio of the other dimension. For instance, I want a 16:9 layout container where the width is "match_parent". However, when using the height as "match_parent", Android does not seem to properly relayout itself. When I set the height to be a ratio of the width everything is fine! Vise versa and it doesn't work. What's going on?

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());
}

}

To see it in action, use a layout like this:

<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>

And this would be my Activity:

Logging output:

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

Why would Android not honor my latest measuredWidth but use an older version but the latest height?

EDIT: Updated. Make the parent of the RatioLayout NOT a RelativeLayout but a LinearLayout or FrameLayout gives me the correct behavior. For some reason RelativeLayout is "caching" the measuredWidth and not using the most current.

EDIT 2: This comment in RelativeLayout.onLayout seems to confirm my "it's caching" which I believe is a bug

@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) {

FINAL EDIT Ok. I give up. This is a legit bug in RelativeLayout. This code kind of fixes it, but it creates issues with the toRightOf properties. The work around that I found was to nest this RatioLayout into another ViewGroup like LinerLayout. Code for those curious

@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);
}

解决方案

I've actually tried to do exactly what you're trying to do before, that is, make a View be as big as possible while retaining some aspect ratio (i.e. square or 4:3 or something like that).

My problem was that when my View was in a ScrollView the size got computed incorrectly. I wasn't able to figure this one out, but I'll post my code below just in case it helps. It's really similar to your code, but I'm inheriting from FrameLayout and I end up calling super.onMeasure().

I double checked the project I was using this in, and I do actually have it as a direct child of 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的无法正常更新的宽度自定义视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆