带有加载指示器的 TextInputLayout
TextInputLayout with loading indicator
使用TextInputLayout from Material Design library we can use various 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
但它被标记为私有,无法在代码中解析。将资源及其所有依赖的私有资源复制到大约 6 个文件中(2 个可绘制对象 + 2 个动画师 + 2 个插值器)。这可能行得通,但感觉很像黑客。
选项 2 错误
我们可以使用ProgressBar
来检索它的indeterminateDrawable
。这种方法的问题在于可绘制对象与 ProgressBar
紧密相关。指示器仅在 ProgressBar
可见时才会显示动画,对一个视图进行着色也会对另一个视图中的指示器进行着色,并且可能会出现其他奇怪的行为。
在类似的情况下,我们可以使用Drawable.mutate()
来获取drawable的新副本。不幸的是 indeterminateDrawable
已经发生变异,因此 mutate()
什么都不做。
真正将可绘制对象与 ProgressBar
分离的是对 indeterminateDrawable.constantState.newDrawable()
的调用。请参阅 documentation 了解更多信息。
无论如何,这仍然感觉像是一个 hack。
好的选项 3
虽然可绘制资源标记为私有,但我们可以解析某些主题属性以获取系统默认加载指示可绘制资源。主题定义了引用 ProgressBar
样式的 progressBarStyle
属性。此样式内部是引用主题可绘制对象的 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()
it:
(drawable as? Animatable)?.start()
颜色
TextInputLayout
receives/loses 焦点时会发生另一个意外行为。在这种情况下,它会根据 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
分别。
您可以使用 Material 组件库提供的 ProgressIndicator
。
在您的布局中只需使用:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textinputlayout"
...>
<com.google.android.material.textfield.TextInputEditText
.../>
</com.google.android.material.textfield.TextInputLayout>
然后定义 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());
最后将可绘制对象应用于 TextInputLayout:
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
textInputLayout.setEndIconDrawable(progressIndicatorDrawable);
转为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());
}
您可以轻松自定义 circularRadius
和 indicatorColors
以及 ProgressIndicator
中定义的所有其他属性:
progressIndicatorSpec.indicatorColors = getResources().getIntArray(R.array.progress_colors);
progressIndicatorSpec.growMode = GROW_MODE_OUTGOING;
使用这个数组:
<integer-array name="progress_colors">
<item>@color/...</item>
<item>@color/....</item>
<item>@color/....</item>
</integer-array>
注意: 至少需要 1.3.0-alpha02
.
版本
这个问题已经有了很好的答案,但是,我想post一个更简洁更简单的解决方案。如果你使用androidx你有一个class继承了Drawable——CircularProgressDrawable,所以你可以使用它。这是我在项目中使用的一段代码:
CircularProgressDrawable drawable = new CircularProgressDrawable(requireContext());
drawable.setStyle(CircularProgressDrawable.DEFAULT);
drawable.setColorSchemeColors(Color.GREEN);
inputLayout.setEndIconOnClickListener(view -> {
inputLayout.setEndIconDrawable(drawable);
drawable.start();
//some long running operation starts...
}
结果:
使用TextInputLayout from Material Design library we can use various 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
但它被标记为私有,无法在代码中解析。将资源及其所有依赖的私有资源复制到大约 6 个文件中(2 个可绘制对象 + 2 个动画师 + 2 个插值器)。这可能行得通,但感觉很像黑客。
选项 2 错误
我们可以使用ProgressBar
来检索它的indeterminateDrawable
。这种方法的问题在于可绘制对象与 ProgressBar
紧密相关。指示器仅在 ProgressBar
可见时才会显示动画,对一个视图进行着色也会对另一个视图中的指示器进行着色,并且可能会出现其他奇怪的行为。
在类似的情况下,我们可以使用Drawable.mutate()
来获取drawable的新副本。不幸的是 indeterminateDrawable
已经发生变异,因此 mutate()
什么都不做。
真正将可绘制对象与 ProgressBar
分离的是对 indeterminateDrawable.constantState.newDrawable()
的调用。请参阅 documentation 了解更多信息。
无论如何,这仍然感觉像是一个 hack。
好的选项 3
虽然可绘制资源标记为私有,但我们可以解析某些主题属性以获取系统默认加载指示可绘制资源。主题定义了引用 ProgressBar
样式的 progressBarStyle
属性。此样式内部是引用主题可绘制对象的 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()
it:
(drawable as? Animatable)?.start()
颜色
TextInputLayout
receives/loses 焦点时会发生另一个意外行为。在这种情况下,它会根据 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
分别。
您可以使用 Material 组件库提供的 ProgressIndicator
。
在您的布局中只需使用:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textinputlayout"
...>
<com.google.android.material.textfield.TextInputEditText
.../>
</com.google.android.material.textfield.TextInputLayout>
然后定义 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());
最后将可绘制对象应用于 TextInputLayout:
textInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
textInputLayout.setEndIconDrawable(progressIndicatorDrawable);
转为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());
}
您可以轻松自定义 circularRadius
和 indicatorColors
以及 ProgressIndicator
中定义的所有其他属性:
progressIndicatorSpec.indicatorColors = getResources().getIntArray(R.array.progress_colors);
progressIndicatorSpec.growMode = GROW_MODE_OUTGOING;
使用这个数组:
<integer-array name="progress_colors">
<item>@color/...</item>
<item>@color/....</item>
<item>@color/....</item>
</integer-array>
注意: 至少需要 1.3.0-alpha02
.
这个问题已经有了很好的答案,但是,我想post一个更简洁更简单的解决方案。如果你使用androidx你有一个class继承了Drawable——CircularProgressDrawable,所以你可以使用它。这是我在项目中使用的一段代码:
CircularProgressDrawable drawable = new CircularProgressDrawable(requireContext());
drawable.setStyle(CircularProgressDrawable.DEFAULT);
drawable.setColorSchemeColors(Color.GREEN);
inputLayout.setEndIconOnClickListener(view -> {
inputLayout.setEndIconDrawable(drawable);
drawable.start();
//some long running operation starts...
}
结果: