使用 TextInputLayout来自 Material Design library我们可以使用各种 end icon modes用于密码编辑、文本清除和自定义模式。此外,如果我们使用任何 Widget.MaterialComponents.TextInputLayout.*.ExposedDropdownMenu
样式,它会自动应用显示打开和关闭 V 形的特殊结束图标模式。
各种图标模式的例子:
考虑到结束图标的各种用例,我们决定在 InputTextLayout
中使用加载指示器,使其看起来像这样:
应该如何着手实现?
最佳答案
可以像这样简单地设置使用自定义可绘制对象来代替结束图标:
textInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
textInputLayout.endIconDrawable = progressDrawable
有问题的部分是获取可绘制的加载指示器。
错误的选项 1
没有可用于加载指示器的公共(public)可绘制资源。
有 android.R.drawable.progress_medium_material
但它被标记为私有(private)并且无法在代码中解析。将资源及其所有依赖的私有(private)资源复制到大约 6 个文件中(2 个可绘制对象 + 2 个动画师 + 2 个插值器)。这可能行得通,但感觉很像黑客。
错误的选项 2
我们可以使用 ProgressBar
来检索它的 indeterminateDrawable
。这种方法的问题在于可绘制对象与 ProgressBar
紧密相关。指示器仅在 ProgressBar
可见时才会显示动画,对一个 View 进行着色也会对另一个 View 中的指示器进行着色,并且可能会出现其他奇怪的行为。
在类似的情况下,我们可以使用 Drawable.mutate()
来获取可绘制对象的新副本。不幸的是,indeterminateDrawable
已经发生了变化,因此 mutate()
什么都不做。
真正使可绘制对象与 ProgressBar
分离的是对 indeterminateDrawable.constantState.newDrawable()
的调用。参见 documentation了解更多信息。
无论如何,这仍然感觉像是一个 hack。
好的选项 3
虽然 drawable 资源被标记为私有(private),但我们可以解析某些主题属性以获取系统默认的加载指示器 drawable。该主题定义了 progressBarStyle
属性,该属性引用了 ProgressBar
的样式。此样式内部是引用主题可绘制对象的 indeterminateDrawable
属性。在代码中,我们可以像这样解析可绘制对象:
fun Context.getProgressBarDrawable(): Drawable {
val value = TypedValue()
theme.resolveAttribute(android.R.attr.progressBarStyleSmall, value, false)
val progressBarStyle = value.data
val attributes = intArrayOf(android.R.attr.indeterminateDrawable)
val array = obtainStyledAttributes(progressBarStyle, attributes)
val drawable = array.getDrawableOrThrow(0)
array.recycle()
return drawable
}
太好了,现在我们有了一个无需修改的原生加载指示器!
额外措施
动画
现在,如果您将可绘制对象插入此代码
textInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
textInputLayout.endIconDrawable = progressDrawable
你会发现它没有显示任何东西。
实际上,它确实正确显示了可绘制对象,但真正的问题是它没有被动画化。碰巧在动画开始时,drawable 折叠成一个不可见的点。
不幸的是,我们无法将 drawable 转换为它的真实类型 AnimationScaleListDrawable
,因为它在 com.android.internal.graphics.drawable
包中。
对我们来说幸运的是,我们可以将其键入为 Animatable
和 start()
:
(drawable as? Animatable)?.start()
颜色
当 TextInputLayout
获得/失去焦点时,会发生另一种意外行为。在这种情况下,它将根据 layout.setEndIconTintList()
定义的颜色为可绘制对象着色。如果您没有明确指定色调列表,它会将可绘制对象着色为 ?colorPrimary
。但是在我们设置 drawable 的那一刻,它仍然着色为 ?colorAccent
并且在看似随机的时刻它会改变颜色。
出于这个原因,我建议使用相同的 ColorStateList
为 layout.endIconTintList
和 drawable.tintList
着色。如:
fun Context.fetchPrimaryColor(): Int {
val array = obtainStyledAttributes(intArrayOf(android.R.attr.colorPrimary))
val color = array.getColorOrThrow(0)
array.recycle()
return color
}
...
val states = ColorStateList(arrayOf(intArrayOf()), intArrayOf(fetchPrimaryColor()))
layout.setEndIconTintList(states)
drawable.setTintList(states)
最终我们得到这样的结果:
分别使用 android.R.attr.progressBarStyle
(中)和 android.R.attr.progressBarStyleSmall
。
关于android - 带有加载指示器的 TextInputLayout,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57661168/