材料式滑块和范围式滑块提示框不总是可见。

11
我希望将工具提示的值始终保持可见,并且工具提示文本应该是背景透明的。 我尝试了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" />

这个回答解决了你的问题吗?在材料范围滑块上添加自定义标签 - javdromero
没有,工具提示仍然消失了。 - avez raj
3个回答

15

从Material Design 1.6.0及以上版本开始 ('com.google.android.material:material:1.6.0'):

有一个官方属性app:labelBehavior="visible",建议按照@S.Gissel的答案进行设置,如下所示:

<com.google.android.material.slider.Slider
    app:labelBehavior="visible"/>

<com.google.android.material.slider.RangeSlider
    app:labelBehavior="visible"/>

从Material Design 1.5.0及以下版本:

使用app:labelBehavior属性没有公共API可以使工具提示始终可见。下面是使用反射的解决方法:

  1. Create a Subclass of a Slider/RangeSlider and override the onDraw(@NonNull Canvas canvas) method and call the setSliderTooltipAlwaysVisible(Slider slider) method to keep the Tooltip always visible like below:

    For Slider:

     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();
         }
     }
    }
    

    For 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();
         }
     }
    }
    

    The key point here is to call the private method private void ensureLabelsAdded() of BaseSlider class using a Reflection after the super.onDraw(canvas) gets called.

  2. Use the above custom Sliders in your xml like below:

    <?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>
    

注意:如果您仍需要在Material Design 1.6.0中使用此解决方法,则必须将上述xml中的属性app:labelBehavior="floating"更改为app:labelBehavior="visible",并且应用于MyCustomSlider/MyCustomRangeSlider。

同时,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>

通过更改backgroundTint颜色,您可以将上述xml中的工具提示背景更改为透明颜色:<item name="backgroundTint">@android:color/transparent</item>

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

slider_transparent_tooltip

带有提示框的结果,背景为不透明:

slider_non_transparent_tooltip


2
这个解决方案乍一看非常出色。我实施了它,但在滚动视图中,工具提示仍然停留在原地,而其余部分会滚动。 :-/ - S. Gissel
@S.Gissel 是的,你说得对,我没有在 ScrollView 中测试它,现在已经修复了,当滚动改变时,你必须使 View 无效。请查看更新后的答案。感谢你让我知道 :) - MariosP
请查看我的回答。终于有一种本地的方法可以不断添加标签了。 - S. Gissel
如果在ScrollView中使用,则标签不会随着其余部分滚动而移动。 - phazei
1
@phazei 在 ScrollView 中使用 Material 库存在一个 bug,你可以按照答案中的解决方法来实现修复,具体操作见 "从 Material Design 1.5.0 及以下版本开始" 节,对于 Material Design 1.6.0 及以上版本,在 xml 文件中使用属性 app:labelBehavior="visible" - MariosP

3

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

我们需要在.xml文件中将TextView放置在Slider上方。您可以为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();
                }
            });
        }
    });

以下方法用于使用滑块滑动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);
}

就是这样!带有自定义标签的Material滑块


3
@mariosP的回答是正确的且不错的。但在Material库1.6.0中,Google添加了一种本机方法来添加一个始终可见的标签。 <Slider app:labelBehavior="visible" />可以完成同样的工作。
实际上,如果仍然使用@mariosP解决方案中的MyCustomSlider并升级到1.6.0,会出现无限调用ondraw()方法且不再显示标签的危险。请小心。

终于在Material Design 1.6.0及以上版本中有了官方处理此问题的方法。唯一的问题是,如果Slider/RangeSlider位于ScrollView内部,则Tooltip不会随其余组件一起滚动,并保持在屏幕的初始位置。希望也能尽快解决这个问题。+1 - MariosP
1
升级到1.6.0版本后,如果您在MyCustomSlider xml文件中将属性app:labelBehavior="floating"更改为app:labelBehavior="visible",则可以避免无限问题,并且在ScrollView中仍然可以使用Tooltip,直到我们从Google获得官方修复。非常感谢这个好建议。 - MariosP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接