自定义方形布局无法按预期工作

时间:2017-09-05 13:22:29

标签: android android-layout

我在使用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);

    }
}

自定义Square Layout工作正常,直到自定义布局变为超出界限的情况。应该有什么应该自动调整到屏幕尺寸,但不会发生。如下所示,CustomSquareLayout实际上延伸到屏幕下方(不可见)。我期望onMeasure能够处理这个问题并给出适当的测量结果。但事实并非如此。 感兴趣的注意事项这里是即使CustomSquareLayout表现得很奇怪,它的子布局都属于方形布局,总是放在左手边。

enter image description here

<!-- 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在顶部)

enter image description here

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

希望在扩展布局时使用onMeasure或任何其他功能找到解决方法(即使某些扩展自定义布局,仍保留Square属性)

编辑1:为了进一步说明,第一种情况的预期结果显示为enter image description here

编辑2:我优先考虑onMeasure()或类似功能,因为需要更早地(在渲染之前)确定布局规格(尺寸)。否则,在组件加载后更改尺寸很简单,但不要求。

4 个答案:

答案 0 :(得分:7)

您可以通过在布局后检查“方形”来强制方形视图。将以下代码添加到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的指定大小的方形视图。不是很优雅,但它很有效。

enter image description here

您还可以在自定义视图中添加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>

答案 1 :(得分: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>

截图:

enter image description here enter image description here

答案 2 :(得分:1)

您无法比较两种测量规格,因为它们不仅仅是尺寸。您可以在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);
}

我希望最终结果大致相同(可能是居中,但这个维度):

final layout mock

请注意,这是使用Gimp完成的图像。

答案 3 :(得分:-1)

试试这个。您可以使用on measure方法创建自定义视图。请查看以下链接了解更多详情。

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