'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:
Create a Subclass of a Slider/RangeSlider and override the
onDraw(@NonNull Canvas canvas)
method and call thesetSliderTooltipAlwaysVisible(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 thesuper.onDraw(canvas)
gets called.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:
Results with a Tooltip always visible with a Non-Transparent background:
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!
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 |