Как программно установить ширину / границу View относительно другого View внутри ConstraintLayout

1

У меня есть ConstraintLayout. Для целей этого примера у нас может быть три представления внутри.

<android.support.constraint.ConstraintLayout ...>

   <TextView
    android:id="@+id/text"
    .../>

   <TextView
    android:id="@+id/value"
    .../>

   <View
    android:id="@+id/bar"
    android:layout_width="0dp"
    android:layout_height="8dp"
    android:background="@color/bar_color"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="@+id/text"
    app:layout_constraintEnd_toStartOf="@id/value"
    />
</android.support.constraint.ConstraintLayout>

Во время выполнения я хочу установить ширину bar на некоторое значение между расстоянием между text и value.

У меня уже есть значение от 0 до 100, которое представляет собой процент от расстояния между двумя текстовыми представлениями.

До сих пор я пробовал два метода, но застрял.

Попытка 1

Для этого мне нужно фактическое расстояние между text и value и метод для правильной установки ширины полосы.

Я могу установить ширину bar, выполнив следующие действия:

val params: ConstraintLayout.LayoutParams = percentageBar.layoutParams as LayoutParams

params.matchConstraintMaxWidth = 300  // should be actual value rather than 300
percentageBar.layoutParams = params

Это устанавливает ширину полосы штрафа. Но мне нужен какой-то способ выяснить, каким должно быть действительное число, а не 300. Что-то вроде (процентное значение * text-value-distance/100).

Попытка 2

Посмотрев на другие ответы по SO, я узнал о процентном ограничении.

...

<View
        android:id="@+id/bar"
        android:layout_width="0dp"
        android:layout_height="8dp"
        android:background="@color/bar_color"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/value"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="@id/text"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent="1.0"/>

...

Затем в коде я могу просто сделать params.matchConstraintPercentWidth = item.percentageOfMaxValue.toFloat()

Проблема с этим заключается в том, что, когда значение процента установлено высоким, оно берет процент от родительского значения и проходит после начала value.

Я иду в правильном направлении?

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

Заранее спасибо.

//Обновить

Я выдвинул упрощенную версию с тем, что напоминает мою проблему. BarProblem на GitHub

Вы можете увидеть проблему на вкладке "Дизайн" редактора макетов.

//Обновление 2

Задача решена. GitHub repo теперь содержит код для решения.

Теги:
android-constraintlayout

2 ответа

0
Лучший ответ

Ключ был в том, что нам нужно подождать, пока не будет разложен ConstraintLayout. Мы можем сделать это с помощью ViewTreeObserver.OnGlobalLayoutListener.

Пример решения:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        text.text = "Text"
        value.text = "Value"

        val globalLayoutListener = MainActivityGlobalListener(item_view, text, value, percentage_bar)

        item_view.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
    }
}

class MainActivityGlobalListener(private val itemView: View,
                                 private val text: View,
                                 private val value: View,
                                 private val bar: View) : ViewTreeObserver.OnGlobalLayoutListener {

    override fun onGlobalLayout() {
        Log.i("yoyo", "in onGlobalLayout")

        val width = value.x - (text.x)
        Log.i("yoyo", "Width: $width")

        val params: ConstraintLayout.LayoutParams = bar.layoutParams as ConstraintLayout.LayoutParams

        params.matchConstraintMaxWidth = (1.0 * width).toInt()  // using 0.1 here - this value is dynamic in real life scenario
        bar.layoutParams = params

        // required to prevent infinite loop
        itemView.viewTreeObserver.removeOnGlobalLayoutListener(this)
    }
}

Обратите внимание на необходимость удалить прослушиватель в последней строке, чтобы предотвратить возникновение бесконечного цикла (прослушивание для макета → установка ширины полосы → триггеры, изменение макета и т.д.)

При использовании Android KTX мы можем упростить до следующего:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        text.text = "Text"
        value.text = "Value"

        item_view.doOnLayout {
            Log.i("yoyo", "in onGlobalLayout")

            val width = value.x - (text.x)
            Log.i("yoyo", "Width: $width")

            val params: ConstraintLayout.LayoutParams = percentage_bar.layoutParams as ConstraintLayout.LayoutParams

            params.matchConstraintMaxWidth = (1.0 * width).toInt()  // using 0.1 here - this value is dynamic in real life scenario
            percentage_bar.layoutParams = params
        }
    }

Спасибо CommonsWare за ответ!

GitHub репо с кодом

0

Хорошее решение для вашего случая - это рекомендации. Эти помощники являются якорями, которые не будут отображаться в вашем приложении, они похожи на одну линию сетки над вашим макетом и могут использоваться для добавления или ограничения ваших виджетов к нему.

  • 0
    Благодарю. Я пробовал это, но, к сожалению, без игры в кости Проблема заключается в том, что установка ширины полосы в процентах от родительского элемента заставляет ее переопределять ограничения и выталкивать за пределы элемента, которому она должна быть ограничена.
  • 0
    app: layout_constraintWidth_max = "300dp" Старайтесь избегать установки ограничений XML с помощью кода.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню