带有加载指示器的 TextInputLayout

Ant*_*nit 2 android loading android-progressbar progress-bar material-components-android

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

各种图标模式示例:

结束图标模式

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

加载指示模式

应该如何着手实施呢?

Ant*_*nit 23

可以简单地设置使用自定义可绘制代替结束图标,如下所示:

textInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
textInputLayout.endIconDrawable = progressDrawable
Run Code Online (Sandbox Code Playgroud)

有问题的部分是获取可绘制的加载指示器。


错误的选项 1

没有可用于加载指示器的公共可绘制资源。

android.R.drawable.progress_medium_material但它被标记为私有并且无法在代码中解析。将资源及其所有依赖的私有资源复制到大约 6 个文件中(2 个可绘制对象 + 2 个动画师 + 2 个插值器)。这可以工作,但感觉很像黑客。


错误的选项 2

我们可以用它ProgressBar来检索它的indeterminateDrawable. 这种方法的问题在于 drawable 与ProgressBar. 指示器仅在ProgressBar可见时动画,为一个视图着色也会为另一个视图中的指示器着色,并且可能还有其他奇怪的行为。

在类似的情况下,我们可以使用Drawable.mutate()获取 drawable 的新副本。不幸的indeterminateDrawable是 已经发生了变异,因此mutate()什么也不做。

真正将可绘制对象与 分离的方法ProgressBar是调用indeterminateDrawable.constantState.newDrawable(). 有关更多见解,请参阅文档

无论如何,这仍然感觉像是一个黑客。


不错的选择 3

尽管可绘制资源被标记为私有,但我们可以解析某些主题属性以获得可绘制的系统默认加载指示器。主题定义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
}
Run Code Online (Sandbox Code Playgroud)

太好了,现在我们有了一个无需黑客即可绘制的本机加载指示器!


额外措施

动画片

现在,如果您将 drawable 插入此代码

textInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
textInputLayout.endIconDrawable = progressDrawable
Run Code Online (Sandbox Code Playgroud)

你会发现它没有显示任何东西。

实际上,它确实正确显示了可绘制对象,但真正的问题是它没有被动画化。碰巧在动画开始时,drawable 被折叠成一个不可见的点。

对我们来说不幸的是,我们无法将 drawable 转换为其真实类型,AnimationScaleListDrawable因为它在com.android.internal.graphics.drawablepackage.json 中。幸运的是,我们可以键入它Animatablestart()它:

(drawable as? Animatable)?.start()
Run Code Online (Sandbox Code Playgroud)

颜色

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

出于这个原因,我建议双方着色layout.endIconTintListdrawable.tintList用相同的ColorStateList。如:

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)
Run Code Online (Sandbox Code Playgroud)

最终我们得到这样的东西:

带有加载指示器的 InputTextLayout

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


Gab*_*tti 6

您可以使用ProgressIndicator材料组件库提供的。

在您的布局中只需使用:

    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textinputlayout"
        ...>
        
        <com.google.android.material.textfield.TextInputEditText
          .../>
        
    </com.google.android.material.textfield.TextInputLayout>
Run Code Online (Sandbox Code Playgroud)

然后定义ProgressIndicator使用:

    ProgressIndicatorSpec progressIndicatorSpec = new ProgressIndicatorSpec();
    progressIndicatorSpec.loadFromAttributes(
            this,
            null,
            R.style.Widget_MaterialComponents_ProgressIndicator_Circular_Indeterminate);

    progressIndicatorSpec.circularInset = 0; // Inset
    progressIndicatorSpec.circularRadius =
            (int) dpToPx(this, 10); // Circular radius is 10 dp.

    IndeterminateDrawable progressIndicatorDrawable =
            new IndeterminateDrawable(
                    this,
                    progressIndicatorSpec,
                    new CircularDrawingDelegate(),
                    new CircularIndeterminateAnimatorDelegate());
Run Code Online (Sandbox Code Playgroud)

最后将可绘制对象应用到 TextInputLayout:

 textInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
 textInputLayout.setEndIconDrawable(progressIndicatorDrawable);
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

转换为dp的是util方法:

public static float dpToPx(@NonNull Context context, @Dimension(unit = Dimension.DP) int dp) {
    Resources r = context.getResources();
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());
}
Run Code Online (Sandbox Code Playgroud)

您可以轻松自定义circularRadiusindicatorColors以及 中定义的所有其他属性ProgressIndicator

    progressIndicatorSpec.indicatorColors = getResources().getIntArray(R.array.progress_colors);
    progressIndicatorSpec.growMode = GROW_MODE_OUTGOING;
Run Code Online (Sandbox Code Playgroud)

用这个数组:

<integer-array name="progress_colors">
    <item>@color/...</item>
    <item>@color/....</item>
    <item>@color/....</item>
</integer-array>
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

注意:它至少需要版本1.3.0-alpha02.