У меня есть 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 теперь содержит код для решения.
Ключ был в том, что нам нужно подождать, пока не будет разложен 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 за ответ!
Хорошее решение для вашего случая - это рекомендации. Эти помощники являются якорями, которые не будут отображаться в вашем приложении, они похожи на одну линию сетки над вашим макетом и могут использоваться для добавления или ограничения ваших виджетов к нему.