Измерение дочерних элементов в настраиваемых перерывах просмотра в RTL

У меня есть собственное представление, которое расширяет LinearLayout и реализует onMeasure. Я бы хотел, чтобы дети были либо настолько широкими, насколько им нужно, либо заполняли доступное пространство.

XML-файлы: Родитель:

<FrameLayout
    xmlns:android = "http://schemas.android.com/apk/res/android"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent">

    <com.example.myapplication.AtMostLinearLayout
        android:id = "@+id/at_most_linear_layout"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content" />

</FrameLayout>

Пример кнопки:

<FrameLayout xmlns:android = "http://schemas.android.com/apk/res/android"
    android:layout_width = "wrap_content"
    android:layout_height = "wrap_content">

    <ImageView
        android:layout_width = "48dp"
        android:layout_height = "48dp"
        android:layout_gravity = "center"
        android:src = "@android:drawable/ic_delete" />
</FrameLayout>

Представления добавляются программно, например:

   findViewById<AtMostLinearLayout>(R.id.at_most_linear_layout).apply {
            repeat(4) {
                LayoutInflater.from(context).inflate(R.layout.button, this)
            }
        }

Наконец, пользовательский класс представления:

 
class AtMostLinearLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {
    private val maxTotalWidth = context.resources.getDimensionPixelOffset(R.dimen.max_buttons_width)
    init {
        orientation = HORIZONTAL
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (childCount < 1) return

        val newWidth = min(measuredWidth, maxTotalWidth)
        var availableWidth = newWidth
        var numberOfLargeChildren = 0
        repeat(childCount) {
            getChildAt(it).let { child ->
                if (child.measuredWidth > availableWidth / childCount) {
                    availableWidth -= child.measuredWidth
                    numberOfLargeChildren++
                }
            }
        }

        val minChildWidth = availableWidth / max(childCount - numberOfLargeChildren, 1)
        repeat(childCount) {
            getChildAt(it).apply {
                measure(
                    MeasureSpec.makeMeasureSpec(max(measuredWidth, minChildWidth), EXACTLY),
                    UNSPECIFIED
                )
            }
        }

        setMeasuredDimension(
            makeMeasureSpec(newWidth, EXACTLY), makeMeasureSpec(measuredHeight, EXACTLY))
    }
}

В LTR работает нормально: Измерение дочерних элементов в настраиваемых перерывах просмотра в RTL Однако в RTL представления по какой-то причине смещены и отображаются вне ViewGroup: Измерение дочерних элементов в настраиваемых перерывах просмотра в RTL

Откуда могло взяться это смещение? Похоже, в часть добавляются звонки для детей, или, по крайней мере, половина...

0
0
84
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Если холст rtl в методе onDraw, вы пытались его инвертировать?

Вы можете попробовать использовать Вид.getLayoutDirection()., чтобы вернуть направление компоновки.

Переопределение метода onDraw и

val isRtl = getLayoutDirection == View.LAYOUT_DIRECTION_RTL

if (isRtrl){
  canvas.scale(-1f, 1f, width / 2, height / 2)
}

К сожалению, это просто переворачивает проблему :)

Novae 15.05.2022 16:41

Вы можете использовать Инспектор макетов (или «показать границы макета» на устройстве), чтобы определить, почему он ведет себя именно так. Расчет смещения по горизонтали, возможно, придется перевернуть; путем вычитания вместо добавления ... для учета изменения направления макета, где абсолютное смещение в пикселях всегда будет пониматься как LTR.

Дело в том, что я не добавляю никакого смещения. Я просто перемеряю дочерние элементы, чтобы убедиться, что они занимают как минимум то пространство, которое им нужно для заполнения всего linearLayout. Последний дочерний элемент в RTL получает левый элемент onLayout, который составляет чуть более половины Viewgroup. Кажется, я не могу понять, где это левое вычисляется.

Novae 15.05.2022 16:42
Ответ принят как подходящий

После прочтения меры LinearLayout & View и кода макета я понял, почему это происходит.

Всякий раз, когда вызывается LinearLayout#measure, вычисляется mTotalLength, который представляет рассчитанную ширину всего представления. Поскольку я вручную пересчитываю дочерние элементы с другим MeasureSpecLinearLayout, я не могу кэшировать эти значения. Позже в проходе layout представления используют кешированный mTotalLength для установки левого дочернего элемента, то есть смещения. Левый основан на гравитации ребенка и, таким образом, зависит от кэшированного значения.

См.: LinearLayout#onlayout

        final int layoutDirection = getLayoutDirection();
        switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
            case Gravity.RIGHT:
                // mTotalLength contains the padding already
                childLeft = mPaddingLeft + right - left - mTotalLength;
                break;

            case Gravity.CENTER_HORIZONTAL:
                // mTotalLength contains the padding already
                childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
                break;

            case Gravity.LEFT:
            default:
                childLeft = mPaddingLeft;
                break;
        }

Я изменил импл, чтобы он всегда устанавливал гравитацию на Gravity.LEFT. Вместо этого мне, вероятно, следует вручную реализовать onLayout!

class AtMostLinearLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : LinearLayout(context, attrs, defStyle) {
    private val maxTotalWidth = context.resources.getDimensionPixelOffset(R.dimen.max_buttons_width)
    init {
        orientation = HORIZONTAL
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (childCount < 1) return

        val newWidth = min(measuredWidth, maxTotalWidth)
        var availableWidth = newWidth
        var numberOfLargeChildren = 0
        repeat(childCount) {
            getChildAt(it).let { child ->
                if (child.measuredWidth > availableWidth / childCount) {
                    availableWidth -= child.measuredWidth
                    numberOfLargeChildren++
                }
            }
        }

        val minChildWidth = availableWidth / max(childCount - numberOfLargeChildren, 1)
        repeat(childCount) {
            getChildAt(it).let { child ->
                child.measure(
                    makeMeasureSpec(max(child.measuredWidth, minChildWidth), EXACTLY),
                    UNSPECIFIED
                )
            }
        }

        // Effectively always set it to Gravity.LEFT to prevent LinearLayout using its
        // internally-cached mTotalLength to set the Child's left.
        gravity = if (layoutDirection == View.LAYOUT_DIRECTION_RTL) Gravity.END else Gravity.START
        setMeasuredDimension(
            makeMeasureSpec(newWidth, EXACTLY), makeMeasureSpec(measuredHeight, EXACTLY))
    }
}

Другие вопросы по теме