Android iconGravity не работает для кнопки Material с индикатором CircularProgressIndicator

#android #material-components-android #material-components #progress-indicator #android-gravity

Вопрос:

Я создал пользовательское представление, расширяющее кнопку MaterialButton, которая заменяет рисоваемый указатель на циклический указатель при загрузке.

Однако, когда я заменяю рисование индикатором CircularProgressIndicator, iconGravity больше не работает. Я не вижу, что я делаю не так.

Я смог свести урок к этому:

 class LoadingButton constructor(context: Context) : MaterialButton(context) {

    private val progressIndicatorDrawable by lazy {
        val progressIndicatorSpec = CircularProgressIndicatorSpec(context, null, 0, R.style.Widget_MaterialComponents_CircularProgressIndicator_ExtraSmall)
        progressIndicatorSpec.indicatorColors = intArrayOf(getColor(context, R.color.white))
        IndeterminateDrawable.createCircularDrawable(context, progressIndicatorSpec).apply {
            setVisible(true, true)
        }
    }

    init {
        // the following drawable is aligned to the start of the view (incorrect).
        icon = progressIndicatorDrawable
        // the following drawable is aligned to the start of the text (correct).
        // icon = DrawableUtil.getDrawable(context, R.drawable.icon_delete)
        text = "Delete"
        iconGravity = ICON_GRAVITY_TEXT_START
    }
}
 

Я использую эту зависимость

 // I've also tried 1.5.0-alpha02
implementation "com.google.android.material:material:1.3.0"
 

Правильное положение Рисование располагается в начале текста.
рисуемое правильное положение

Неверное положение Индикатор выполнения больше не отображается в начале текста. неверное положение индикатора прогресса

Комментарии:

1. Похоже, это ошибка. Можете ли вы использовать compose в качестве обходного пути?

2. Я должен был бы разобраться в этом. У меня еще нет никакого опыта работы с композицией.

3. Как и предполагали другие, это похоже на ошибку в библиотеке компонентов материалов de, поэтому я сообщил о проблеме: github.com/material-components/material-components-android/…

Ответ №1:

Любопытно, что один рисунок работает, а другой-нет. Возможно, стоит сообщить об этом как об ошибке. В то же время я могу предложить обходной путь, который не требует много работы (IMO).

Если мы возьмем ваш пользовательский вид и удалим iconGravity и установим гравитацию текста в начале и вертикальном центре:

 gravity = Gravity.START or Gravity.CENTER_VERTICAL
 

мы можем объединить неопределенное и текст в левой части кнопки:

введите описание изображения здесь

Теперь вопрос в том, как расположить пару по центру кнопки. Мы можем воспользоваться подсказкой из кода кнопки «Материал» о подходе к тому, сколько места требуется слева для центрирования рисунка и текста.

 int newIconLeft =
    (buttonWidth
        - getTextWidth()
        - ViewCompat.getPaddingEnd(this)
        - localIconSize
        - iconPadding
        - ViewCompat.getPaddingStart(this))
        / 2;
 

Складываю все это воедино:

введите описание изображения здесь

Вот демонстрационный код. Это может потребовать некоторой настройки, если ваша фактическая кнопка более сложная, чем изображена в вашем вопросе.

 class LoadingButton @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : MaterialButton(context, attrs) {

    // true - use indeterminate; false = use delete icon
    private val useIndeterminate = true

    private val progressIndicatorDrawable by lazy {
        val progressIndicatorSpec = CircularProgressIndicatorSpec(
            context,
            null,
            0,
            R.style.Widget_MaterialComponents_CircularProgressIndicator_ExtraSmall
        )
        progressIndicatorSpec.indicatorColors = intArrayOf(getColor(context, R.color.white))
        IndeterminateDrawable.createCircularDrawable(context, progressIndicatorSpec).apply {
            setVisible(true, true)
        }
    }

    init {
        if (useIndeterminate) {
            icon = progressIndicatorDrawable
            gravity = Gravity.START or Gravity.CENTER_VERTICAL
        } else {
            icon = ContextCompat.getDrawable(context, R.drawable.icon_delete)
            iconGravity = ICON_GRAVITY_TEXT_START
            gravity = Gravity.CENTER
        }
        text = "Delete"
    }

    // This is homw much we need to shift the contents of the button to the right in order to
    // center it.
    private var xShift = 0f
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (useIndeterminate) {
            xShift = (width
                    - icon.intrinsicWidth
                    - paint.measureText(text.toString())
                    - iconPadding
                    - ViewCompat.getPaddingStart(this)
                    - ViewCompat.getPaddingEnd(this)) / 2f
        }
    }

    override fun onDraw(canvas: Canvas?) {
        // Shift right before drawing.
        canvas?.withTranslation(xShift) {
            super.onDraw(canvas)
        }
    }
}
 

Комментарии:

1. Это приемлемый обходной путь для этой ошибки. Мне нужно было немного подправить код, и это решило мою проблему.

Ответ №2:

Отказ от ответственности: похоже, это ошибка библиотеки компонентов материалов, и этот ответ не является решением для кода, указанного в ответе.

Если вы можете использовать Compose в качестве обходного пути, просто примените что-то вроде:

 Button (
    modifier = Modifier.width(150.dp),
    onClick = { /* Do something! */ }
){
    CircularProgressIndicator(color = White,
        modifier = Modifier
            .size(24.dp)
            .align(Alignment.CenterVertically))
    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
    Text("Delete")
}
 

введите описание изображения здесь

Чтобы включить содержимое пользовательского интерфейса Compose в существующий макет, просто добавьте xml-макет:

 <androidx.compose.ui.platform.ComposeView
        android:id="@ id/compose_view"
        android:layout_width="150dp"
        android:layout_height="wrap_content" />
 

и в вашем Фрагменте или деятельности:

     val view: ComposeView = findViewById(R.id.compose_view)
    view.apply {
        setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
        setContent {
            MaterialTheme {
                /* The code above */
                Button (
                    onClick = { /* Do something! */ }){
                    CircularProgressIndicator(color = White, modifier = Modifier.size(24.dp).align(
                        Alignment.CenterVertically))
                    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
                    Text("Delete")
                }
            }
        }
    }
 

Здесь вы можете найти более подробную информацию о совместимости.

Комментарии:

1. Есть еще один ответ, который не требует от меня переноса этого кода для создания. Однако это пригодится, как только мы начнем переходить к составлению. Спасибо за ваши усилия.