【问题标题】:TextInputLayout with loading indicator带有加载指示器的 TextInputLayout
【发布时间】:2019-12-30 20:06:05
【问题描述】:

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

各种图标模式示例:

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

应该如何着手实施?

【问题讨论】:

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


    【解决方案1】:

    这个问题已经有了很好的答案,但是,我想发布一个更简洁,更简单的解决方案。如果你使用 androidx 你有一个继承 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...
    }
    

    结果:

    【讨论】:

      【解决方案2】:

      您可以使用材料组件库提供的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());
      

      最后将drawable应用到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());
      }
      

      您可以轻松自定义circularRadiusindicatorColors 以及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版本。

      【讨论】:

      • 如何隐藏这个进度??
      【解决方案3】:

      人们可以像这样简单地设置使用自定义drawable代替End Icon:

      textInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
      textInputLayout.endIconDrawable = progressDrawable
      

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


      错误选项 1

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

      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 属性,它引用了主题可绘制对象。在代码中,我们可以像这样解析drawable:

      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
      }
      

      太好了,现在我们有了一个无需 hack 即可绘制的原生加载指示器!


      额外措施

      动画

      现在如果你把drawable插入这段代码

      textInputLayout.endIconMode = TextInputLayout.END_ICON_CUSTOM
      textInputLayout.endIconDrawable = progressDrawable
      

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

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

      不幸的是,我们无法将可绘制对象转换为其真实类型AnimationScaleListDrawable,因为它位于com.android.internal.graphics.drawable 包中。 幸运的是,我们可以将其输入为Animatablestart() 它:

      (drawable as? Animatable)?.start()
      

      颜色

      TextInputLayout 接收/失去焦点时,会发生另一个意外行为。在这种情况下,它将根据layout.setEndIconTintList() 定义的颜色为drawable 着色。如果您没有明确指定着色列表,它会将可绘制对象着色为?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)
      

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

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

      【讨论】:

      • 好文章!很有用
      • 很好的答案,谢谢,它有效。请注意,您可以使用自定义颜色而不是 Android 颜色。如果您使用的是 Compat 库,则可以像 requireContext().getColorStateList(R.color.yourcolor)ContextCompat.getColorStateList(requireContext(), R.color.yourColor) 一样执行此操作,只需使用 layout.setEndIconTintList(states) 添加状态列表
      • 非常感谢,先生,这个带有代码示例的声明性解释。
      • 如何使用getProgressBarDrawable
      • 如果根本不想着色怎么办。为什么谷歌假设每个人都想要色彩?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多