Я пытаюсь согласовать следующие две вещи:
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?
Единственное решение, которое я могу придумать, будет:
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);
}
}