android - 带有加载指示器的 TextInputLayout

标签 android progress-bar loading android-progressbar material-components-android

使用 TextInputLayout来自 Material Design library我们可以使用各种 end icon modes用于密码编辑、文本清除和自定义模式。此外,如果我们使用任何 Widget.MaterialComponents.TextInputLayout.*.ExposedDropdownMenu 样式,它会自动应用显示打开和关闭 V 形的特殊结束图标模式。

各种图标模式的例子:

End Icon Modes

考虑到结束图标的各种用例,我们决定在 InputTextLayout 中使用加载指示器,使其看起来像这样:

Loading indicator mode

应该如何着手实现?

最佳答案

可以像这样简单地设置使用自定义可绘制对象来代替结束图标:

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 包中。 对我们来说幸运的是,我们可以将其键入为 Animatablestart() :

(drawable as? Animatable)?.start()

颜色

TextInputLayout 获得/失去焦点时,会发生另一种意外行为。在这种情况下,它将根据 layout.setEndIconTintList() 定义的颜色为可绘制对象着色。如果您没有明确指定色调列表,它会将可绘制对象着色为 ?colorPrimary。但是在我们设置 drawable 的那一刻,它仍然着色为 ?colorAccent 并且在看似随机的时刻它会改变颜色。

出于这个原因,我建议使用相同的 ColorStateListlayout.endIconTintListdrawable.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)

最终我们得到这样的结果:

InputTextLayout with loading indicators

分别使用 android.R.attr.progressBarStyle(中)和 android.R.attr.progressBarStyleSmall

关于android - 带有加载指示器的 TextInputLayout,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57661168/

相关文章:

css - 从远程主机上的 CSS 样式表加载图像

android - Android 工业手持终端?

android - 为 onTouch() android 编写单元测试

php - 进度条分割

php - 制作 PHP CSS 和 HTML 进度条

css - 网站部分加载背景图片(Bootstrap)

Android 加载 JS 包失败

java - float[] 16 位转 byte[]

android - 如何在与服务器建立连接期间使 progressBar 顺利进行?

javascript - 在 Assets 加载 Vue 应用程序时显示加载屏幕