Как преодолеть проблему псевдонимов, вызванную правилами ConstraintLayout?

Я пытаюсь согласовать следующие две вещи:

A) Мне нужен точный, равномерный и чистый пользовательский интерфейс с несколькими кнопками одинакового размера, которые точно соответствуют базовым "ячейкам сетки" - пользовательский интерфейс, который будет выглядеть максимально похожим (пропорционально размеру экрана) на всех устройствах Android, как возможный.

B) На Android размеры экрана (соотношение сторон и фактические числа пикселей) устройства пользователя неизвестны (приложению) до времени выполнения.

Мое решение для этого было: (есть пример кода ниже!)

1) заблокировать приложение в портретном режиме,

2) Не определяйте ничего в статических / абсолютных терминах, таких как dp, px и т. Д., А вместо этого концептуализируйте "базовую единицу измерения", которая является функцией высоты экрана - в моем случае 0,08%, - и основывайте все на этом,

3) Установите горизонтальные направляющие в пределах ConstraintLayout, позиции которого выражены в процентах от высоты родительского (экранного) экрана.

4) Заставьте все кнопки использовать эту "базовую единицу" в качестве высоты и ширины, установив для атрибута XML layout_constraintDimensionRatio значение "1:1" и используя приведенные выше рекомендации (см. Шаг 3),

5) Выполните позиционирование и размеры всех видов, используя ограничения для этих указаний, границ родительского элемента или одной дополнительной вертикальной направляющей на 50% ширины экрана.

Проблема заключается в том, что в зависимости от высоты пикселя экрана (будь то нечетный или четный... или, возможно, других факторов), размеры вида / кнопки (и, следовательно, пути, нарисованные внутри нее) ограничены между пара направляющих точно не совпадает с другой точкой зрения, нарисованной между другой парой... даже если расстояние между обеими парами направляющих должно быть одинаковым в процентах от высоты родителей.:)

Вот пример, показывающий эмулятор Nexus 4:

введите описание изображения здесь

Сначала я подумал, что проблема заключается просто в округлении "ошибки" при вычислениях размеров Android, но потом, почему представление не будет квадратным, даже если им предписан атрибут отношения 1: 1?

Единственные решения, о которых я могу думать, были бы:

А) Чтобы сделать макет программно, а не с XML... и установить положения ориентиров как точные положения пикселей вместо процентов, и ответить на вопрос, "что такое 0,08 x высота экрана?" Я сам... делаю соответствующие исправления, чтобы компенсировать "неделимые" высоты экрана.

Б) Переопределите onLayout() в пользовательских представлениях и "заставьте" их измерения быть согласованными... но тогда это противоречит цели руководящих принципов.:(

Но я очень надеюсь, что есть более простое решение, чем А или Б.

(Я знаю, что кто-то собирается предложить 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" />

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

    }

}

0 ответов

Я бы пошел на второй план: используйте свой 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);
    }
}
Другие вопросы по тегам