Как преодолеть проблему псевдонимов, вызванную правилами ConstraintLayout? - PullRequest
0 голосов
/ 01 декабря 2018

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

A) Мне нужен точный, равномерный и чистый пользовательский интерфейс с несколькими кнопками одинакового размера, которые точно соответствуют базовым «ячейкам сетки» - пользовательский интерфейсэто будет выглядеть как можно больше (пропорционально размеру экрана) на максимально возможном количестве устройств 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 голосов
/ 03 декабря 2018

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