【问题标题】:material slider and range slider tooltip not always visible材质滑块和范围滑块工具提示并不总是可见
【发布时间】:2021-08-01 18:16:37
【问题描述】:

我想让工具提示值始终可见,并且工具提示的文本应该是背景透明的。 我试过https://github.com/material-components/material-components-android/blob/master/docs/components/Slider.md,但没有办法让工具提示始终可见

<com.google.android.material.slider.Slider
        android:id="@+id/slider_sound_sensitivity"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:valueFrom="0.0"
        android:valueTo="100.0"
        android:layout_marginTop="@dimen/_8sdp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.508"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txt_sound_sensitivity" />

<com.google.android.material.slider.RangeSlider
        android:id="@+id/range_humidity_in_percentage"
        style="@style/Myslider"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:valueFrom="0.0"
        android:valueTo="100.0"
        android:layout_marginTop="@dimen/_16sdp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txt_humidity_in_percentage"
        app:values="@array/initial_slider_values" />

【问题讨论】:

标签: android material-components-android


【解决方案1】:

目前根据Sliders - Material Design Documentation,没有公共API 可以使用app:labelBehavior 属性保持工具提示始终可见。下面我将描述一种使用反射的可能解决方案。

  1. 创建 Slider/RangeSlider 的子类并覆盖 onDraw(@NonNull Canvas canvas) 方法并调用 setSliderTooltipAlwaysVisible(Slider slider) 方法以保持 Tooltip 始终可见,如下所示:

    对于滑块:

     public class MyCustomSlider extends Slider {
    
     public MyCustomSlider(@NonNull Context context) {
         super(context);
         init();
     }
    
     public MyCustomSlider(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         init();
     }
    
     public MyCustomSlider(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         init();
     }
    
     private void init(){
         //in case this View is inside a ScrollView you can listen to OnScrollChangedListener to redraw the View
         getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
             @Override
             public void onScrollChanged() {
                 invalidate();
             }
         });
     }
    
     @Override
     protected void onDraw(@NonNull Canvas canvas) {
         super.onDraw(canvas);
         setSliderTooltipAlwaysVisible(this);
     }
    
     public static void setSliderTooltipAlwaysVisible(Slider slider){
    
         try
         {
             Class<?> baseSliderCls = Slider.class.getSuperclass();
             if (baseSliderCls != null) {
    
                 Method ensureLabelsAddedMethod = baseSliderCls.getDeclaredMethod("ensureLabelsAdded");
                 ensureLabelsAddedMethod.setAccessible(true);
                 ensureLabelsAddedMethod.invoke(slider);
             }
         }
         catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
             e.printStackTrace();
         }
     }
    }
    

    对于 RangeSlider:

     public class MyCustomRangeSlider extends RangeSlider {
    
     public MyCustomRangeSlider(@NonNull Context context) {
         super(context);
         init();
     }
    
     public MyCustomRangeSlider(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
         init();
     }
    
     public MyCustomRangeSlider(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         init();
     }
    
     private void init(){
         //in case this View is inside a ScrollView you can listen to OnScrollChangedListener to redraw the View
         getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
             @Override
             public void onScrollChanged() {
                 invalidate();
             }
         });
     }
    
     @Override
     protected void onDraw(@NonNull Canvas canvas) {
         super.onDraw(canvas);
         setSliderTooltipAlwaysVisible(this);
     }
    
     public static void setSliderTooltipAlwaysVisible(RangeSlider slider){
    
         try
         {
             Class<?> baseSliderCls = RangeSlider.class.getSuperclass();
             if (baseSliderCls != null) {
    
                 Method ensureLabelsAddedMethod = baseSliderCls.getDeclaredMethod("ensureLabelsAdded");
                 ensureLabelsAddedMethod.setAccessible(true);
                 ensureLabelsAddedMethod.invoke(slider);
             }
         }
         catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
             e.printStackTrace();
         }
     }
    }
    

    这里的重点是在super.onDraw(canvas)被调用后,使用Reflection调用BaseSlider类的私有方法private void ensureLabelsAdded()

  2. 在您的 xml 中使用上述自定义滑块,如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    
    <my.package.name.MyCustomSlider
        style="@style/Widget.App.Slider"
        app:labelBehavior="floating"
        android:id="@+id/slider_sound_sensitivity"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:valueFrom="0.0"
        android:valueTo="100.0"
        android:layout_marginTop="@dimen/_8sdp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    
    <my.package.name.MyCustomRangeSlider
        style="@style/Widget.App.Slider"
        app:labelBehavior="floating"
        android:id="@+id/range_humidity_in_percentage"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:valueFrom="0.0"
        android:valueTo="100.0"
        android:layout_marginTop="@dimen/_16sdp"
        app:layout_constraintTop_toBottomOf="@+id/slider_sound_sensitivity"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:values="@array/initial_slider_values" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    

style="@style/Widget.App.Slider" 是您在 styles.xml 文件中定义的自定义样式,如下所示:

   <style name="Widget.App.Slider" parent="Widget.MaterialComponents.Slider">
        <item name="materialThemeOverlay">@style/ThemeOverlay.App.Slider</item>
        <item name="labelStyle">@style/Widget.App.Tooltip</item>
    </style>

    <style name="ThemeOverlay.App.Slider" parent="">
        <item name="colorPrimary">@android:color/holo_red_light</item>
        <item name="colorOnSurface">@android:color/holo_red_light</item>
    </style>

    <style name="Widget.App.Tooltip" parent="Widget.MaterialComponents.Tooltip">
        <item name="android:textAppearance">@style/TextAppearance.App.Tooltip</item>
        <!--This is the Tooltip Background Color. In case you don't want a background change it to @android:color/transparent -->
        <item name="backgroundTint">@android:color/holo_orange_light</item>
    </style>

    <style name="TextAppearance.App.Tooltip" parent="TextAppearance.MaterialComponents.Tooltip">
        <item name="android:textColor">@android:color/holo_blue_light</item>
    </style>

从上面的 xml 中,您可以通过更改 backgroundTint 颜色将工具提示背景更改为透明颜色:&lt;item name="backgroundTint"&gt;@android:color/transparent&lt;/item&gt;

带有透明背景的工具提示始终可见的结果:

带有工具提示的结果在非透明背景下始终可见:

【讨论】:

  • 这个解决方案一见钟情。我实现了它,但在滚动视图中,工具提示保持不变,而其余部分滚动。 :-/
  • @S.Gissel 是的,你是对的,我没有在 ScrollView 中对其进行测试,现在它已得到修复,当滚动更改时你必须使 View 无效()。检查更新的答案。谢谢你让我知道:)
【解决方案2】:

目前 Material Slider 没有 始终可见的标签状态。 但是我们可以做一件事来克服这个问题。 首先,我们必须在 Material Slider 中创建app:labelBehavior:gone

我们必须将 TextView 放在 Slider 上方的 .xml 文件中。您可以对 TextView 应用不同的背景。在下面的代码中 tvSlider 是 TextView 的 id。

然后调用Material Slider的addOnChangeListener方法,并使用下面的代码。

slider.addOnChangeListener(new Slider.OnChangeListener() {
        @Override
        public void onValueChange(@NonNull Slider slider, float value, boolean fromUser) {
            tvSlider.setText(String.valueOf(Math.round(value)));
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    updateValueLabelPosition();
                }
            });
        }
    });

下面的方法是用 Slider 滑动 TextView。

private void updateValueLabelPosition() {
    float valueRangeSize = slider.getValueTo() - slider.getValueFrom();
    float valuePercent = (slider.getValue() - slider.getValueFrom()) / valueRangeSize;
    float valueXDistance = valuePercent * slider.getTrackWidth();
    float offset = slider.getX() + slider.getTrackSidePadding() - ((float) tvSlider.getWidth() / 2);
    tvSlider.setX(valueXDistance + offset);
}

就是这样!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多