material slider and range slider tooltip not always visible
Asked Answered
F

3

11

i wanted to keep the tooltip value visible always and also the text of tooltip should be background transparent. i tried https://github.com/material-components/material-components-android/blob/master/docs/components/Slider.md but there no way to keep the tooltip always visible

<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" />
Fervent answered 1/8, 2021 at 18:16 Comment(2)
Does this answer your question? Add a custom label to material range sliderShun
no still the tooltip gets goneFervent
P
18

From Material Design 1.6.0 and above ('com.google.android.material:material:1.6.0'):

There is an official attribute app:labelBehavior="visible" as suggested by @S.Gissel on his answer like the below:

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

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

From Material Design 1.5.0 and below:

There was no public API to keep the Tooltip always visible using the app:labelBehavior attribute. Below is a workaround using a Reflection:

  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>
    

Note: In case you still need to use this workaround in Material Design 1.6.0 you have to change from the above xml the attribute app:labelBehavior="floating" to app:labelBehavior="visible" in both MyCustomSlider/MyCustomRangeSlider.

And style="@style/Widget.App.Slider" is your custom style defined in styles.xml file like below:

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

From the above xml you can change the Tooltip Background to Transparent color by changing the backgroundTint color: <item name="backgroundTint">@android:color/transparent</item>.

Results with a Tooltip always visible with a Transparent background:

slider_transparent_tooltip

Results with a Tooltip always visible with a Non-Transparent background:

slider_non_transparent_tooltip

Piliferous answered 9/8, 2021 at 11:53 Comment(5)
This solution is wonderful on first sight. I implemented it, but inside a scrollview the tooltip stays, while the rest scrolls. :-/Bertrambertrand
@S.Gissel yes you are right i didn't test it inside a ScrollView now it has been fixed you have to invalidate() the View when the scroll is changed. Check updated answer. Thanks for letting me know :)Piliferous
please see my answer to this question. Finally a native way to add a label constantly.Bertrambertrand
If used in a ScrollView, then labels don't move while the rest scrollsLaurelaureano
@Laurelaureano There is a bug in the Material library in case is used in a ScrollView, you can follow the workaround in the answer to fix it by implementing the section "From Material Design 1.5.0 and below" and in the xml use the attribute app:labelBehavior="visible" from Material Design 1.6.0 and above.Piliferous
B
3

Currently Material Slider has not state of label for ALWAYS VISIBLE. But we can do one thing to overcome this issue. First we have to make app:labelBehavior:gone in Material Slider.

We have to put TextView above Slider in .xml file. You can apply different background to TextView. In below code tvSlider is an id of TextView.

Then call addOnChangeListener method of Material Slider, And use below code.

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

Below method is for sliding TextView with Slider.

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

That's it!Material Slider with Custom label

Bootless answered 21/10, 2021 at 9:28 Comment(0)
B
3

@mariosP's answer is valid and good. But in Material library 1.6.0 Google added a native way to add a label that's always visible.

<Slider app:labelBehavior="visible" /> does the same job.

In fact it is even dangerous upgrading to 1.6.0 and still using a MyCustomSlider as in @mariosP's solution. The ondraw() method is called infinitely and no label is showing anymore. Be careful.

Bertrambertrand answered 7/5, 2022 at 20:7 Comment(2)
Finally an official way to handle this in Material Design 1.6.0 and above. The only issue is if the Slider/RangeSlider is inside a ScrollView the Tooltip doesn't scroll with the rest components and stays to its initial position on the screen. Hope to fix this also soon.+1Piliferous
After upgrading to 1.6.0, you can avoid the infinitely issue if you change in MyCustomSlider xml file the attribute app:labelBehavior="floating" to app:labelBehavior="visible" and the Tooltip it will still works inside a ScrollView until we have an official fix from Google. Good point thank you.Piliferous

© 2022 - 2024 — McMap. All rights reserved.