如何克服ConstraintLayout准则引起的混淆问题?

时间:2018-12-01 12:33:09

标签: java android user-interface android-constraintlayout aliasing

我正试图调和以下两件事:

A)我想要一个精确,统一且干净的UI,并具有几个大小完全相同的按钮,这些按钮与底层的“网格单元”完全对应-一个看起来尽可能多(与屏幕大小成比例)的UI尽可能使用Android设备。

B)在Android上,直到运行时,用户设备的屏幕尺寸(长宽比和实际像素数)才未知(对于应用程序而言)。

我对此的解决方法是:(下面有一个代码示例!

1)将应用锁定为纵向模式,

2)请勿以静态/绝对术语(例如dp,px等)定义任何内容,而应将“基本度量单位”概念化,它是屏幕高度的函数-在我的情况下为0.08%-并以所有内容为基础

3)在ConstraintLayout中设置水平辅助线,其位置表示为父级(屏幕)高度的百分比。

4)通过将其XML layout_constraintDimensionRatio属性设置为“ 1:1”并使用上述准则,使所有按钮都将此“基本单位”用作其高度和宽度(请参阅第3步),

5)通过使用对这些准则,父级边界或屏幕宽度50%处的一个附加垂直准则的约束来完成所有视图的位置和尺寸。

问题在于,取决于屏幕的像素高度(无论它是奇数还是偶数……或其他因素),视图/按钮的尺寸(以及在其中绘制的路径)约束在一对辅助线之间并不完全匹配另一对辅助线之间绘制的另一视图的视图...即使两对辅助线之间的距离应与父级高度的百分比相同。 :)

以下是显示Nexus 4仿真器的示例:

enter image description here

起初我以为问题只是由于在Android尺寸计算过程中四舍五入“错误”而引起的,但是为什么即使规定了1:1比例属性,视图也不会是正方形的?

我能想到的唯一解决方案是:

A)要以编程方式而不是XML方式进行布局...,并将准则位置设置为确切的像素位置而不是百分比,然后回答问题:“ 0.08 x屏幕高度是多少?”我自己...进行适当的更正以补偿“不可分割的”屏幕高度。

B)在自定义视图中覆盖onLayout()并“强制”其尺寸保持一致...但这会违反准则的目的。 :(

但是我真的希望有比A或B更简单的解决方案。

(我知道有人会建议使用GridLayout,但出于某些原因,这不是一个选择...其中之一是,在GridLayout中,必须将单元格内的视图设置为wrap_content ...,这意味着它们的路径绘图无法在运行时相对于父对象生成。

不过,还有其他建议。

代码示例:

我在下面整理了一个简单的“最小示例”,应该可以轻松在Android Studio中对其进行重构。如果不是很明显,日志将显示该问题。

XML布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/rootView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <android.support.constraint.Guideline
        android:id="@+id/guidelineHorizontalTop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.08" />

    <android.support.constraint.Guideline
        android:id="@+id/guidelineHorizontalBottom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.92" />

    <android.support.constraint.Guideline
        android:id="@+id/guidelineHorizontalCenter1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.38" />

    <android.support.constraint.Guideline
        android:id="@+id/guidelineHorizontalCenter2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.46" />

    <android.support.constraint.Guideline
        android:id="@+id/guidelineHorizontalCenter3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.54" />

    <android.support.constraint.Guideline
        android:id="@+id/guidelineHorizontalCenter4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.62" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonTopLeft"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonTopLeft"
        app:layout_constraintBottom_toTopOf="@+id/guidelineHorizontalTop"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonTopRight"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonTopRight"
        app:layout_constraintBottom_toTopOf="@+id/guidelineHorizontalTop"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonBottomLeft"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonBottomLeft"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guidelineHorizontalBottom" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonBottomRight"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonBottomRight"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guidelineHorizontalBottom" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonMiddle"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonMiddle"
        app:layout_constraintBottom_toBottomOf="@id/guidelineHorizontalCenter3"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="@id/guidelineHorizontalCenter2" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonMiddleTopLeft"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonMiddleTopLeft"
        app:layout_constraintBottom_toBottomOf="@id/guidelineHorizontalCenter2"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintEnd_toStartOf="@id/buttonMiddle"
        app:layout_constraintTop_toTopOf="@id/guidelineHorizontalCenter1" />

    <com.example.boober.stack_aliasingproblem.CustomButton
        android:id="@+id/buttonMiddleTopRight"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:tag="buttonMiddleTopRight"
        app:layout_constraintBottom_toBottomOf="@id/guidelineHorizontalCenter2"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintStart_toEndOf="@id/buttonMiddle"
        app:layout_constraintTop_toTopOf="@id/guidelineHorizontalCenter1" />
</android.support.constraint.ConstraintLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    CustomButton buttonTopLeft;
    CustomButton buttonTopRight;

    CustomButton buttonMiddle;
    CustomButton buttonMiddleTopLeft;
    CustomButton getButtonMiddleTopRight;

    CustomButton buttonBottomLeft;
    CustomButton buttonBottomRight;

    CustomButton[] arrayOfCustomButtons;

    ConstraintLayout rootView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonTopLeft = findViewById(R.id.buttonTopLeft);
        buttonTopRight = findViewById(R.id.buttonTopRight);
        buttonBottomLeft = findViewById(R.id.buttonBottomLeft);
        buttonBottomRight = findViewById(R.id.buttonBottomRight);
        buttonMiddle = findViewById(R.id.buttonMiddle);
        buttonMiddleTopLeft = findViewById(R.id.buttonMiddleTopLeft);
        getButtonMiddleTopRight = findViewById(R.id.buttonMiddleTopRight);

        arrayOfCustomButtons = new CustomButton[]{buttonTopLeft, buttonTopRight, buttonBottomLeft,
                buttonBottomRight, buttonMiddle, buttonMiddleTopLeft, getButtonMiddleTopRight};
        rootView = findViewById(R.id.rootView);

        for (final CustomButton cb : arrayOfCustomButtons) {
            cb.setClickable(true);
            cb.post(new Runnable() {
                @Override
                public void run() {
                    Log.i("XXX", "width of: " + cb.getTag() + " is: "
                            + cb.getMeasuredWidth());
                }
            });
        }

        rootView.post(new Runnable() {
            @Override
            public void run() {
                Log.i("XXX", "height of rootView is: " + rootView.getMeasuredHeight());
            }
        });
    }

}

CustomButton.java:

public class CustomButton extends View {

    Path myOutlinePath;

    Paint myThinPaintBrush;
    Paint myThickPaintBrush;

    boolean isHighlighted = false;

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

    public CustomButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        float measuredWidth = getMeasuredWidth();

        Log.i("XXX", "measured WIDTH Of " + this.getTag() + " is: " + measuredWidth);
        Log.i("XXX", "measured HEIGT Of " + this.getTag() + " is: " + getMeasuredHeight());
        Log.i("XXX", "\n ");

        generateMyOutline(measuredWidth);

        myThinPaintBrush.setStrokeWidth(measuredWidth/12);
        myThickPaintBrush.setStrokeWidth(measuredWidth/6);

    }

    private void generateMyOutline(float W) {

        Path path = new Path();

        path.moveTo(0,0);
        path.lineTo(W, 0);
        path.lineTo(W, W);
        path.lineTo(0, W);
        path.lineTo(0,0);

        myOutlinePath = path;

    }

    private void init() {

        myOutlinePath = new Path();

        myThinPaintBrush = new Paint();
        myThinPaintBrush.setAntiAlias(false); // setting this to true does not solve the problem.
        myThinPaintBrush.setStyle(Paint.Style.STROKE);
        myThinPaintBrush.setStrokeCap(Paint.Cap.ROUND);

        myThickPaintBrush = new Paint();
        myThickPaintBrush.setAntiAlias(false);
        myThickPaintBrush.setStyle(Paint.Style.STROKE);
        myThickPaintBrush.setStrokeCap(Paint.Cap.ROUND);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (this.isClickable()) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    isHighlighted = true;
                    invalidate();
                    break;

                case MotionEvent.ACTION_UP:
                    isHighlighted = false;
                    invalidate();
                    break;

                case MotionEvent.ACTION_CANCEL:
                    isHighlighted = false;
                    invalidate();
                    break;
            }
        }
        return super.onTouchEvent(event);

    }

    @Override
    protected void onDraw(Canvas canvas) {

        canvas.drawPath(myOutlinePath, myThinPaintBrush);
        if (isHighlighted) {
            canvas.drawPath(myOutlinePath, myThickPaintBrush);
        }
        super.onDraw(canvas);

    }

}

1 个答案:

答案 0 :(得分:1)

我会提出中间立场:按原样使用XML布局,并以编程方式对准则位置进行调整。以下代码通过计算新的布局高度(其为初始布局高度的8%的倍数),将百分比参考线转换为固定位置参考线。

所有大小均已正确计算,但底部正方形通常会更大。可以根据您的实际要求轻松地对此进行更正(例如,更重要的是位于底部或与其他正方形的距离一定。)

MainActivity.jav

public class MainActivity extends AppCompatActivity {

    CustomButton buttonTopLeft;
    CustomButton buttonTopRight;

    CustomButton buttonMiddle;
    CustomButton buttonMiddleTopLeft;
    CustomButton getButtonMiddleTopRight;

    CustomButton buttonBottomLeft;
    CustomButton buttonBottomRight;

    CustomButton[] arrayOfCustomButtons;

    ConstraintLayout rootView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        buttonTopLeft = findViewById(R.id.buttonTopLeft);
        buttonTopRight = findViewById(R.id.buttonTopRight);
        buttonBottomLeft = findViewById(R.id.buttonBottomLeft);
        buttonBottomRight = findViewById(R.id.buttonBottomRight);
        buttonMiddle = findViewById(R.id.buttonMiddle);
        buttonMiddleTopLeft = findViewById(R.id.buttonMiddleTopLeft);
        getButtonMiddleTopRight = findViewById(R.id.buttonMiddleTopRight);

        rootView = findViewById(R.id.rootView);

        rootView.post(new Runnable() {
            @Override
            public void run() {
                int rootViewHeight = rootView.getMeasuredHeight();
                Log.i("XXX", "height of rootView is: " + rootViewHeight);
                int segHeight = (int) (rootViewHeight * 0.08f);
                adjustGuideline(R.id.guidelineHorizontalTop, segHeight);
                adjustGuideline(R.id.guidelineHorizontalCenter1, segHeight);
                adjustGuideline(R.id.guidelineHorizontalCenter2, segHeight);
                adjustGuideline(R.id.guidelineHorizontalCenter3, segHeight);
                adjustGuideline(R.id.guidelineHorizontalCenter4, segHeight);
                adjustGuideline(R.id.guidelineHorizontalBottom, segHeight);

                arrayOfCustomButtons = new CustomButton[]{buttonTopLeft, buttonTopRight, buttonBottomLeft,
                    buttonBottomRight, buttonMiddle, buttonMiddleTopLeft, getButtonMiddleTopRight};
                rootView = findViewById(R.id.rootView);
                for (final CustomButton cb : arrayOfCustomButtons) {
                    cb.setClickable(true);
                    cb.post(new Runnable() {
                        @Override
                        public void run() {
                            Log.i("MainActivity", "<<<< width of: " + cb.getTag() + " is: "
                                + cb.getMeasuredWidth());
                        }
                    });
                }
            }
        });
    }

    private void adjustGuideline(int guideLineId, int segHeight) {
        Guideline gl = (Guideline) findViewById(guideLineId);
        ConstraintLayout.LayoutParams lp = ((ConstraintLayout.LayoutParams) gl.getLayoutParams());
        gl.setGuidelineBegin((int) (segHeight * lp.guidePercent / 0.08f));
        gl.setGuidelinePercent(-1f);
    }
}