'material slider and range slider tooltip not always visible

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


Solution 1:[1]

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

Solution 2:[2]

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

Solution 3:[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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 S. Gissel
Solution 3 Dhruv Patel