【问题标题】:Custom Square Layout not working as expected自定义方形布局未按预期工作
【发布时间】:2018-02-13 19:34:42
【问题描述】:

我在使用custom Square Layout 时偶然发现了这个问题:通过扩展 布局并覆盖其onMeasure() 方法以使尺寸= 两者中的较小(高度或宽度)。

以下是自定义布局代码:

public class CustomSquareLayout extends RelativeLayout{


    public CustomSquareLayout(Context context) {
        super(context);
    }

    public CustomSquareLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomSquareLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CustomSquareLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //Width is smaller
        if(widthMeasureSpec < heightMeasureSpec)
            super.onMeasure(widthMeasureSpec, widthMeasureSpec);

            //Height is smaller
        else
            super.onMeasure(heightMeasureSpec, heightMeasureSpec);

    }
}

自定义方形布局可以正常工作,直到自定义布局超出屏幕范围。但是,应该自动调整到屏幕尺寸的东西不会发生。如下所示,CustomSquareLayout 实际上延伸到屏幕下方(不可见)。我期望onMeasure 处理这个问题,并给出适当的测量值。但事实并非如此。 有趣的地方是,即使CustomSquareLayout 的行为很奇怪,它的子布局都属于始终放置在左侧的方形布局。

<!-- XML for above image -->
<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"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="300dp"
        android:text="Below is the Square Layout"
        android:gravity="center"
        android:id="@+id/text"
        />

    <com.app.application.CustomSquareLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/text"
        android:background="@color/colorAccent"             #PINK
        android:layout_centerInParent="true"
        android:id="@+id/square"
        android:padding="16dp"
        >
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"            #Note this
            android:background="@color/colorPrimaryDark"    #BLUE
            >
        </RelativeLayout>
    </com.app.application.CustomSquareLayout>

</RelativeLayout>

正常情况:(Textview在顶部)

以下是我引用的几个链接:

希望在扩展布局时使用onMeasure或任何其他函数来解决这个问题(这样即使有些扩展了自定义布局,Square属性仍然存在)

编辑1:为了进一步澄清,第一种情况的预期结果显示为

编辑 2:我偏爱onMeasure() 或需要提前(渲染之前)确定布局规范(尺寸)的功能。否则在组件加载后更改尺寸很简单,但不需要。

【问题讨论】:

  • 在此处链接此issue,因为它是相关的。问题实际上出在父 RelativeLayout 上,它不尊重(出于某种原因)我们计算的 width &amp; height。尝试将其更改为 LinearLayoutFrameLayout 它应该可以工作。我还在研究它,但现在没有时间。

标签: android android-layout


【解决方案1】:

试试这个。您可以使用 on measure 方法来制作自定义视图。查看下面的链接了解更多详情。

http://codecops.blogspot.in/2017/06/how-to-make-responsive-imageview-in.html

【讨论】:

    【解决方案2】:

    您可以通过在布局后检查“方形度”来强制使用方形视图。将以下代码添加到onCreate()

    final View squareView = findViewById(R.id.square);
    squareView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            squareView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            if (squareView.getWidth() != squareView.getHeight()) {
                int squareSize = Math.min(squareView.getWidth(), squareView.getHeight());
                RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) squareView.getLayoutParams();
                lp.width = squareSize;
                lp.height = squareSize;
                squareView.requestLayout();
            }
        }
    });
    

    这将强制使用替换MATCH_PARENT 的指定大小重新测量和布局方形视图。不是非常优雅,但它确实有效。

    您还可以将PreDraw listener 添加到您的自定义视图中。

    onPreDraw

    boolean onPreDraw ()

    即将绘制视图树时调用的回调方法。此时,树中的所有视图都已被测量并给出了一个框架。客户可以使用它来调整他们的滚动范围,甚至在绘制之前请求一个新的布局。

    返回 true 以继续当前的绘制过程,或返回 false 以取消。

    在自定义视图的每个构造函数中添加一个初始化方法的调用:

    private void init() {
        this.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                if (getWidth() != getHeight()) {
                    int squareSize = Math.min(getWidth(), getHeight());
                    RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
                    lp.width = squareSize;
                    lp.height = squareSize;
                    requestLayout();
                    return false;
                }
                return true;
            }
        });
    }
    

    XML 可能如下所示:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="300dp"
            android:gravity="center"
            android:text="Below is the Square Layout" />
    
        <com.example.squareview.CustomSquareLayout
            android:id="@+id/square"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/text"
            android:background="@color/colorAccent"
            android:padding="16dp">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerInParent="true"
                android:background="@color/colorPrimaryDark" />
        </com.example.squareview.CustomSquareLayout>
    
    </RelativeLayout>
    

    【讨论】:

    • 对不起,我之前没有提到这一点(已更新问题)。一旦在屏幕上绘制自定义布局,我不想重新渲染它。所以需要一些像onMeasure这样的方法,它在渲染之前计算尺寸。并且还应该能够为将来的目的而扩展。
    • @KaushikNP 涉及onPreDraw()的方法会在渲染图像之前计算边界。屏幕上没有任何东西会闪烁或似乎被重绘。试试吧。如果我了解您的要求,我认为它会满足您的要求。
    • 抱歉之前没有注意到。 onPreDraw() 正是我所需要的!!!万分感谢!接受你的回答。
    • 只有一个微不足道的问题(我可以生存,但如果可能的话想解决),是 Android Studio 中用于 xml 的 Preview 选项不会呈现 Square(尽管它在应用程序)。以下链接是其截图:https://imgur.com/a/C92Zo。我想你对此无能为力,但是如果我使用onMeasure 方法,如果它是动态的,为什么它可以正常工作?
    • @KaushikNP 我又看了看如何让预览工作。调用requestLayout() 的常用方法似乎在编辑模式下不起作用。我不知道如何让预览正常工作。可能值得提出另一个问题来专门解决该问题。如果还有什么想不通的,我会发在这里的。
    【解决方案3】:

    您无法比较这两种测量规格,因为它们不仅仅是一个尺寸。你可以在this answer 看到一个很好的解释。此答案适用于自定义视图,但测量规格是相同的。您需要获取模式和大小来计算最终大小,并比较两个维度的最终结果。

    在您分享的第二个示例中,正确的问题是this one(第三个答案)。用 C# 为 Xamarin 编写,但易于理解。

    对您来说失败的情况是因为您找到了AT_MOST 模式(当视图到达屏幕底部时),这就是在这种情况下比较失败的原因。

    那应该是最终的方法(可能有错别字,我一直无法测试:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
        int width, height;
    
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
                width = Math.min(widthSize, heightSize);
                break;
            default:
                width = 100;
                break;
        }
    
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = Math.min(widthSize, heightSize);
                break;
            default:
                height = 100;
                break;
        }
    
        var size = Math.min(width, height);
        var newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
        super.onMeasure(newMeasureSpec, newMeasureSpec);
    }
    

    我希望最终的结果大致是这样的(也许居中,但是这个尺寸):

    请注意,这是使用 Gimp 制作的图像。

    【讨论】:

    • 我试用了代码。没用。和以前一样。浏览您发布的链接。可能会在那里找到有用的东西。
    • 两件事:一模一样?紫色方块现在应该更小了,除非我遗漏了什么。其次,由于您的代码中的比较,我误解了您所说的内容。我认为您需要使正方形适合,但您需要它们剪辑,就像@benp 第一个屏幕截图,不是吗?
    • 我需要它是一个正方形。粉红色(外部正方形)应该剪裁,并且它的宽度也应该减小,因为它应该是一个正方形。
    • 我添加了一张图片来大致说明我理解的最终结果。那是对的吗? (也许居中,但那将是下一步)
    • 计时伙伴。我也是(编辑中添加的图像)。是的,它是正确的。我希望如此。但我从代码中得到的并不是那样。
    【解决方案4】:

    视图的测量宽度和视图的宽度之间存在差异(高度相同)。 onMeasure 只是设置视图的测量尺寸。绘图过程中还有一个不同的部分会限制视图的实际尺寸,以便它们不会超出父级。

    如果我添加此代码:

        final View square = findViewById(R.id.square);
        square.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                System.out.println("measured width: " + square.getMeasuredWidth());
                System.out.println("measured height: " + square.getMeasuredHeight());
                System.out.println("actual width: " + square.getWidth());
                System.out.println("actual height: " + square.getHeight());
            }
        });
    

    我在日志中看到了这一点:

    09-05 10:19:25.768  4591  4591 I System.out:  measured width: 579
    09-05 10:19:25.768  4591  4591 I System.out:  measured height: 579
    09-05 10:19:25.768  4591  4591 I System.out:  actual width: 768
    09-05 10:19:25.768  4591  4591 I System.out:  actual height: 579
    

    如何通过创建自定义视图来解决?我不知道;我从来没有学过。但我确实知道如何解决它,而无需编写任何 Java 代码:使用ConstraintLayout

    ConstraintLayout 支持子级应该能够使用纵横比设置其尺寸的想法,因此您可以简单地使用1 的比率并获得一个方形子级。这是我更新的布局(关键是app:layout_constraintDimensionRatio attr):

    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="300dp"
            android:gravity="center"
            android:text="Below is the Square Layout"
            app:layout_constraintTop_toTopOf="parent"/>
    
        <RelativeLayout
            android:id="@+id/square"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:padding="16dp"
            android:background="@color/colorAccent"
            app:layout_constraintTop_toBottomOf="@+id/text"
            app:layout_constraintDimensionRatio="1">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerInParent="true"
                android:background="@color/colorPrimaryDark">
            </RelativeLayout>
    
        </RelativeLayout>
    
    </android.support.constraint.ConstraintLayout>
    

    还有截图:

    【讨论】:

    • 同意ConstraingLayout 是一个更好的解决方案。如果他可以更改布局以使用它,那就更好了,面向未来。
    • 是的,我确实知道ConstraintLayout,是的,我同意它是一种更好的方法,而且谷歌对此施加了很大的压力。但我仍然想知道如何使这项工作与其他布局一起使用。
    • 还有一个小提示,第一张截图并不是真正的方形。感谢您指出测量宽度和实际宽度。这或许可以解释发生了什么。
    • @KaushikNP 你的意思是它被剪裁了,不是吗?
    • @XavierRubioJansana,是的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-17
    • 1970-01-01
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 2018-03-09
    相关资源
    最近更新 更多