/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.material.slider; import com.google.android.material.R; import static com.google.android.material.internal.ThemeEnforcement.createThemedContext; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.ColorInt; import androidx.annotation.DimenRes; import androidx.annotation.Dimension; import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.view.InflateException; import android.view.MotionEvent; import android.view.View; import com.google.android.material.internal.ThemeEnforcement; import com.google.android.material.resources.MaterialResources; import java.util.Locale; /** * A widget that allows picking a value within a given range by sliding a thumb along a horizontal * line. * *
The slider can function either as a continuous slider, or as a discrete slider. The mode of * operation is controlled by the value of the step size. If the step size is set to 0, the slider * operates as a continuous slider where the slider's thumb can be moved to any position along the * horizontal line. If the step size is set to a number greater than 0, the slider operates as a * discrete slider where the slider's thumb will snap to the closest tick mark. See {@link * #setStepSize(float)}. * *
In continuous mode the slider displays a line on which the thumb can be dragged to select a * value. * *
In discrete mode the slider displays a line on which the thumb can be dragged to select a * value. When clicked, discrete tick marks are displayed along the line, and the thumb * automatically snaps to the closest tick mark. When clicked or focused, a bubble displaying the * selected value is shown above the horizontal line. * *
The {@link OnChangeListener} interface defines a callback to be invoked when the slider * changes. * *
The {@link LabelFormatter} interface defines a formatter to be used to render text within the * bubble shown while in discrete mode. * *
{@link BasicLabelFormatter} is a simple implementation of the {@link LabelFormatter} that * displays the selected value using letters to indicate magnitude (e.g.: 1.5K, 3M, 12B, etc..). * *
With the default style {@link * com.google.android.material.R.style.Widget_MaterialComponents_Slider}, colorPrimary is used to * customize the color of the slider. The following attributes are used to customize the slider's * appearance further: * *
The following XML attributes are used to set the slider's various parameters of operation: * *
Note: the slider does not accept {@link View.OnFocusChangeListener}s. * * @attr ref com.google.android.material.R.styleable#Slider_android_value * @attr ref com.google.android.material.R.styleable#Slider_android_valueFrom * @attr ref com.google.android.material.R.styleable#Slider_android_valueTo * @attr ref com.google.android.material.R.styleable#Slider_android_stepSize * @attr ref com.google.android.material.R.styleable#Slider_trackColor * @attr ref com.google.android.material.R.styleable#Slider_activeTrackColor * @attr ref com.google.android.material.R.styleable#Slider_inactiveTrackColor * @attr ref com.google.android.material.R.styleable#Slider_thumbColor * @attr ref com.google.android.material.R.styleable#Slider_tickColor * @attr ref com.google.android.material.R.styleable#Slider_activeTickColor * @attr ref com.google.android.material.R.styleable#Slider_inactiveTickColor * @attr ref com.google.android.material.R.styleable#Slider_labelColor */ public class Slider extends View { private static final String TAG = Slider.class.getSimpleName(); private static final String EXCEPTION_ILLEGAL_VALUE = "Slider value must be greater or equal to valueFrom, and lower or equal to valueTo"; private static final String EXCEPTION_ILLEGAL_DISCRETE_VALUE = "Value must be equal to valueFrom plus a multiple of stepSize when using stepSize"; private static final String EXCEPTION_ILLEGAL_VALUE_FROM = "valueFrom must be smaller than valueTo"; private static final String EXCEPTION_ILLEGAL_VALUE_TO = "valueTo must be greater than valueFrom"; private static final String EXCEPTION_ILLEGAL_STEP_SIZE = "The stepSize must be 0, or a factor of the valueFrom-valueTo range"; private static final int HALO_ALPHA = 63; private static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_Slider; @NonNull private final Paint inactiveTrackPaint; @NonNull private final Paint activeTrackPaint; @NonNull private final Paint thumbPaint; @NonNull private final Paint haloPaint; @NonNull private final Paint ticksPaint; @NonNull private final Drawable label; @NonNull private final Paint labelTextPaint; @NonNull private final Rect labelTextBounds; @NonNull private String labelText = ""; private int widgetHeight; private int widgetHeightDiscrete; private int lineHeight; private int trackSidePadding; private int trackTop; private int trackTopDiscrete; private int thumbPaddingDisabled; private int thumbRadius; private int haloRadius; private int labelWidth; private int labelHeight; private int labelTopOffset; private float labelTextSize; private int labelTextTopOffset; private OnChangeListener listener; private LabelFormatter formatter; private boolean thumbIsPressed = false; private float valueFrom; private float valueTo; private float thumbPosition = 0.0f; // The position of the thumb normalised to a [0.0, 1.0] range. private float stepSize = 0.0f; private float[] ticksCoordinates; private int trackWidth; @NonNull private ColorStateList inactiveTrackColor; @NonNull private ColorStateList activeTrackColor; @NonNull private ColorStateList thumbColor; @NonNull private ColorStateList tickColor; @NonNull private ColorStateList textColor; /** Interface definition for a callback invoked when a slider's value is changed. */ public interface OnChangeListener { void onValueChange(Slider slider, float value); } /** * Interface definition for applying custom formatting to the text displayed inside the bubble * shown when a slider is used in discrete mode. */ public interface LabelFormatter { @NonNull String getFormattedValue(float value); } /** * A simple implementation of the {@link LabelFormatter} interface, that limits the number * displayed inside a discrete slider's bubble to three digits, and a single-character suffix that * denotes magnitude (e.g.: 1.5K, 2.2M, 1.3B, 2T). */ public static final class BasicLabelFormatter implements LabelFormatter { private static final long TRILLION = 1000000000000L; private static final int BILLION = 1000000000; private static final int MILLION = 1000000; private static final int THOUSAND = 1000; @NonNull @Override public String getFormattedValue(float value) { if (value >= TRILLION) { return String.format(Locale.US, "%.1fT", value / TRILLION); } else if (value >= BILLION) { return String.format(Locale.US, "%.1fB", value / BILLION); } else if (value >= MILLION) { return String.format(Locale.US, "%.1fM", value / MILLION); } else if (value >= THOUSAND) { return String.format(Locale.US, "%.1fK", value / THOUSAND); } else { return String.format(Locale.US, "%.0f", value); } } } public Slider(@NonNull Context context) { this(context, null); } public Slider(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, R.attr.sliderStyle); } public Slider(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(createThemedContext(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr); // Ensure we are using the correctly themed context rather than the context that was passed in. context = getContext(); loadResources(context.getResources()); processAttributes(context, attrs, defStyleAttr); inactiveTrackPaint = new Paint(); inactiveTrackPaint.setStyle(Paint.Style.STROKE); inactiveTrackPaint.setStrokeWidth(lineHeight); activeTrackPaint = new Paint(); activeTrackPaint.setStyle(Paint.Style.STROKE); activeTrackPaint.setStrokeWidth(lineHeight); thumbPaint = new Paint(); haloPaint = new Paint(); haloPaint.setStyle(Paint.Style.FILL); ticksPaint = new Paint(); ticksPaint.setStyle(Paint.Style.STROKE); ticksPaint.setStrokeWidth(lineHeight); label = context.getResources().getDrawable(R.drawable.mtrl_slider_label); label.setColorFilter( new PorterDuffColorFilter(getColorForState(thumbColor), PorterDuff.Mode.MULTIPLY)); labelTextPaint = new Paint(); labelTextPaint.setTextSize(labelTextSize); labelTextBounds = new Rect(); super.setOnFocusChangeListener( new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { invalidate(); } }); setFocusable(true); setFocusableInTouchMode(true); } @Override public void setOnFocusChangeListener(View.OnFocusChangeListener listener) {} private void loadResources(@NonNull Resources resources) { widgetHeight = resources.getDimensionPixelSize(R.dimen.mtrl_slider_widget_height); widgetHeightDiscrete = resources.getDimensionPixelSize(R.dimen.mtrl_slider_widget_height_discrete); lineHeight = resources.getDimensionPixelSize(R.dimen.mtrl_slider_line_height); trackSidePadding = resources.getDimensionPixelOffset(R.dimen.mtrl_slider_track_side_padding); trackTop = resources.getDimensionPixelOffset(R.dimen.mtrl_slider_track_top); trackTopDiscrete = resources.getDimensionPixelOffset(R.dimen.mtrl_slider_track_top_discrete); thumbPaddingDisabled = resources.getDimensionPixelOffset(R.dimen.mtrl_slider_thumb_padding_disabled); labelWidth = resources.getDimensionPixelSize(R.dimen.mtrl_slider_label_width); labelHeight = resources.getDimensionPixelSize(R.dimen.mtrl_slider_label_height); labelTopOffset = resources.getDimensionPixelSize(R.dimen.mtrl_slider_label_top_offset); labelTextSize = resources.getDimension(R.dimen.mtrl_slider_label_text_size); labelTextTopOffset = resources.getDimensionPixelSize(R.dimen.mtrl_slider_label_text_top_offset); } private void processAttributes(Context context, AttributeSet attrs, int defStyleAttr) { TypedArray a = ThemeEnforcement.obtainStyledAttributes( context, attrs, R.styleable.Slider, defStyleAttr, DEF_STYLE_RES); valueFrom = a.getFloat(R.styleable.Slider_android_valueFrom, 0.0f); valueTo = a.getFloat(R.styleable.Slider_android_valueTo, 1.0f); setValue(a.getFloat(R.styleable.Slider_android_value, valueFrom)); stepSize = a.getFloat(R.styleable.Slider_android_stepSize, 0.0f); boolean hasTrackColor = a.hasValue(R.styleable.Slider_trackColor); int inactiveTrackColorRes = hasTrackColor ? R.styleable.Slider_trackColor : R.styleable.Slider_inactiveTrackColor; int activeTrackColorRes = hasTrackColor ? R.styleable.Slider_trackColor : R.styleable.Slider_activeTrackColor; inactiveTrackColor = MaterialResources.getColorStateList(context, a, inactiveTrackColorRes); activeTrackColor = MaterialResources.getColorStateList(context, a, activeTrackColorRes); thumbColor = MaterialResources.getColorStateList(context, a, R.styleable.Slider_thumbColor); tickColor = MaterialResources.getColorStateList(context, a, R.styleable.Slider_activeTickColor); textColor = MaterialResources.getColorStateList(context, a, R.styleable.Slider_labelColor); thumbRadius = a.getDimensionPixelSize(R.styleable.Slider_thumbRadius, 0); haloRadius = a.getDimensionPixelSize(R.styleable.Slider_haloRadius, 0); a.recycle(); validateValueFrom(); validateValueTo(); validateStepSize(); } private void validateValueFrom() { if (valueFrom >= valueTo) { Log.e(TAG, EXCEPTION_ILLEGAL_VALUE_FROM); throw new IllegalArgumentException(EXCEPTION_ILLEGAL_VALUE_FROM); } } private void validateValueTo() { if (valueTo <= valueFrom) { Log.e(TAG, EXCEPTION_ILLEGAL_VALUE_TO); throw new IllegalArgumentException(EXCEPTION_ILLEGAL_VALUE_TO); } } private void validateStepSize() { if (stepSize < 0.0f) { Log.e(TAG, EXCEPTION_ILLEGAL_STEP_SIZE); throw new IllegalArgumentException(EXCEPTION_ILLEGAL_STEP_SIZE); } else if (stepSize > 0.0f && (valueTo - valueFrom) % stepSize != 0.0f) { Log.e(TAG, EXCEPTION_ILLEGAL_STEP_SIZE); throw new IllegalArgumentException(EXCEPTION_ILLEGAL_STEP_SIZE); } } /** Returns the slider's {@code valueFrom} value. */ public float getValueFrom() { return valueFrom; } /** * Sets the slider's {@code valueFrom} value. * *
The {@code valueFrom} value must be strictly lower than the {@code valueTo} value. If that * is not the case, an {@link IllegalArgumentException} will be thrown. * * @param valueFrom The minimum value for the slider's range of values * @throws IllegalArgumentException If {@code valueFrom} is greater or equal to {@code valueTo} */ public void setValueFrom(float valueFrom) { this.valueFrom = valueFrom; validateValueFrom(); } /** Returns the slider's {@code valueTo} value. */ public float getValueTo() { return valueTo; } /** * Sets the slider's {@code valueTo} value. * *
The {@code valueTo} value must be strictly greater than the {@code valueFrom} value. If that * is not the case, an {@link IllegalArgumentException} will be thrown. * * @param valueTo The maximum value for the slider's range of values * @throws IllegalArgumentException If {@code valueTo} is lesser or equal to {@code valueFrom} */ public void setValueTo(float valueTo) { this.valueTo = valueTo; validateValueTo(); } /** Returns the value of the slider. */ public float getValue() { return thumbPosition * (valueTo - valueFrom) + valueFrom; } /** * Sets the value of the slider. * *
The thumb value must be greater or equal to {@code valueFrom}, and lesser or equal to {@code * valueTo}. If that is not the case, an {@link IllegalArgumentException} will be thrown. * *
If the slider is in discrete mode (i.e. the tick increment value is greater than 0), the * thumb's value must be set to a value falls on a tick (i.e.: {@code value == valueFrom + x * * stepSize}, where {@code x} is an integer equal to or greater than 0). If that is not the case, * an {@link IllegalArgumentException} will be thrown. * * @param value The value to which to set the slider * @throws IllegalArgumentException If the value is not within {@code valueFrom} and {@code * valueTo}. If stepSize is greater than 0 and value does not fall on a tick */ public void setValue(float value) { if (isValueValid(value)) { thumbPosition = (value - valueFrom) / (valueTo - valueFrom); if (hasOnChangeListener()) { listener.onValueChange(this, getValue()); } } } private boolean isValueValid(float value) { if (value < valueFrom || value > valueTo) { Log.e(TAG, EXCEPTION_ILLEGAL_VALUE); return false; } if (stepSize > 0.0f && ((valueFrom - value) % stepSize) != 0.0f) { Log.e(TAG, EXCEPTION_ILLEGAL_DISCRETE_VALUE); return false; } return true; } /** * Returns the step size used to mark the ticks. * *
A step size of 0 means that the slider is operating in continuous mode. A step size greater * than 0 means that the slider is operating in discrete mode. */ public float getStepSize() { return stepSize; } /** * Sets the step size to use to mark the ticks. * *
Setting this value to 0 will make the slider operate in continuous mode. Setting this value * to a number greater than 0 will make the slider operate in discrete mode. * *
The step size must evenly divide the range described by the {@code valueFrom} and {@code * valueTo}, it must be a factor of the range. If the step size is not a factor of the range, an * {@link IllegalArgumentException} will be thrown. * *
Setting this value to a negative value will result in an {@link IllegalArgumentException}.
*
* @param stepSize The interval value at which ticks must be drawn. Set to 0 to operate the slider
* in continuous mode and not have any ticks.
* @throws IllegalArgumentException If the step size is not a factor of the {@code
* valueFrom}-{@code valueTo} range. If the step size is less than 0
*/
public void setStepSize(float stepSize) {
this.stepSize = stepSize;
validateStepSize();
requestLayout();
}
/**
* Returns {@code true} if the slider has an {@link OnChangeListener} attached, {@code false}
* otherwise.
*/
public boolean hasOnChangeListener() {
return listener != null;
}
/**
* Registers a callback to be invoked when the slider changes.
*
* @param listener The callback to run when the slider changes
*/
public void setOnChangeListener(@Nullable OnChangeListener listener) {
this.listener = listener;
}
/**
* Returns {@code true} if the slider has a {@link LabelFormatter} attached, {@code false}
* otherwise.
*/
public boolean hasLabelFormatter() {
return formatter != null;
}
/**
* Registers a {@link LabelFormatter} to be used to format the value displayed in the bubble shown
* when the slider operates in discrete mode.
*
* @param formatter The {@link LabelFormatter} to use to format the bubble's text
*/
public void setLabelFormatter(@Nullable LabelFormatter formatter) {
this.formatter = formatter;
}
/** Sets the radius of the thumb in pixels. */
public void setThumbRadius(@IntRange(from = 0) @Dimension int radius) {
thumbRadius = radius;
postInvalidate();
}
/** Sets the radius of the thumb from a dimension resource. */
public void setThumbRadiusResource(@DimenRes int radius) {
setThumbRadius(getResources().getDimensionPixelSize(radius));
}
/** Returns the radius of the thumb. */
@Dimension
public int getThumbRadius() {
return thumbRadius;
}
/** Sets the radius of the halo in pixels. */
public void setHaloRadius(@IntRange(from = 0) @Dimension int radius) {
haloRadius = radius;
postInvalidate();
}
/** Sets the radius of the halo from a dimension resource. */
public void setHaloRadiusResource(@DimenRes int radius) {
setHaloRadius(getResources().getDimensionPixelSize(radius));
}
/** Returns the radius of the halo. */
@Dimension()
public int getHaloRadius() {
return haloRadius;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(
stepSize > 0.0f ? widgetHeightDiscrete : widgetHeight, MeasureSpec.EXACTLY));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateTrackWidthAndTicksCoordinates(w);
}
private void updateTrackWidthAndTicksCoordinates(int viewWidth) {
trackWidth = viewWidth - trackSidePadding * 2;
if (stepSize > 0.0f) {
int tickCount = (int) ((valueTo - valueFrom) / stepSize + 1);
if (ticksCoordinates == null || ticksCoordinates.length != tickCount * 2) {
ticksCoordinates = new float[tickCount * 2];
}
float interval = trackWidth / (float) (tickCount - 1);
for (int i = 0; i < tickCount * 2; i += 2) {
ticksCoordinates[i] = trackSidePadding + i / 2 * interval;
ticksCoordinates[i + 1] = trackTopDiscrete;
}
}
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
int top = stepSize > 0.0f ? trackTopDiscrete : trackTop;
drawTrack(canvas, trackWidth, top);
if (thumbPosition > 0.0f) {
drawMarker(canvas, trackWidth, top);
}
if (stepSize > 0.0f) {
if (hasFocus() && isEnabled()) {
drawLabel(canvas, trackWidth, top);
drawLabelText(canvas, trackWidth, top);
} else {
drawThumb(canvas, trackWidth, top);
}
if (thumbIsPressed) {
drawTicks(canvas);
}
} else {
if (!thumbIsPressed && hasFocus() && isEnabled()) {
drawHalo(canvas, trackWidth, top);
}
drawThumb(canvas, trackWidth, top);
}
}
private void drawTrack(@NonNull Canvas canvas, int width, int top) {
float right = trackSidePadding + thumbPosition * width;
if (!isEnabled()) {
right += thumbRadius + thumbPaddingDisabled;
}
if (right < trackSidePadding + width) {
canvas.drawLine(right, top, trackSidePadding + width, top, inactiveTrackPaint);
}
}
private void drawMarker(@NonNull Canvas canvas, int width, int top) {
float left = trackSidePadding + thumbPosition * width;
if (!isEnabled()) {
left -= thumbPaddingDisabled + thumbRadius;
}
canvas.drawLine(trackSidePadding, top, left, top, activeTrackPaint);
}
private void drawTicks(@NonNull Canvas canvas) {
canvas.drawPoints(ticksCoordinates, ticksPaint);
}
private void drawLabel(@NonNull Canvas canvas, int width, int top) {
int left = trackSidePadding + (int) (thumbPosition * width) - labelWidth / 2;
top -= labelTopOffset;
label.setBounds(left, top, left + labelWidth, top + labelHeight);
label.draw(canvas);
}
private void drawLabelText(@NonNull Canvas canvas, int width, int top) {
labelTextPaint.getTextBounds(labelText, 0, labelText.length(), labelTextBounds);
int left = trackSidePadding + (int) (thumbPosition * width) - labelTextBounds.width() / 2;
canvas.drawText(labelText, left, top - labelTextTopOffset, labelTextPaint);
}
private void drawThumb(@NonNull Canvas canvas, int width, int top) {
if (isEnabled()) {
thumbPaint.setStyle(Paint.Style.FILL);
} else {
thumbPaint.setStyle(Paint.Style.STROKE);
thumbPaint.setStrokeWidth(lineHeight);
}
canvas.drawCircle(trackSidePadding + thumbPosition * width, top, thumbRadius, thumbPaint);
}
private void drawHalo(@NonNull Canvas canvas, int width, int top) {
canvas.drawCircle(trackSidePadding + thumbPosition * width, top, haloRadius, haloPaint);
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (!isEnabled()) {
return false;
}
float x = event.getX();
float position = (x - trackSidePadding) / trackWidth;
position = Math.max(0, position);
position = Math.min(1, position);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
requestFocus();
thumbIsPressed = true;
thumbPosition = position;
snapThumbPosition();
invalidate();
if (hasOnChangeListener()) {
listener.onValueChange(this, getValue());
}
break;
case MotionEvent.ACTION_MOVE:
thumbPosition = position;
snapThumbPosition();
invalidate();
if (hasOnChangeListener()) {
listener.onValueChange(this, getValue());
}
break;
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
thumbIsPressed = false;
thumbPosition = position;
snapThumbPosition();
invalidate();
break;
default:
// Nothing to do in this case.
}
float value = getValue();
if (hasLabelFormatter()) {
labelText = formatter.getFormattedValue(value);
} else {
labelText = String.format((int) value == value ? "%.0f" : "%.2f", value);
}
return true;
}
private void snapThumbPosition() {
if (stepSize > 0.0f) {
int intervalsCovered = Math.round(thumbPosition * (ticksCoordinates.length / 2 - 1));
thumbPosition = (float) intervalsCovered / (ticksCoordinates.length / 2 - 1);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
inactiveTrackPaint.setColor(getColorForState(inactiveTrackColor));
activeTrackPaint.setColor(getColorForState(activeTrackColor));
ticksPaint.setColor(getColorForState(tickColor));
labelTextPaint.setColor(getColorForState(textColor));
thumbPaint.setColor(getColorForState(thumbColor));
haloPaint.setColor(getColorForState(thumbColor));
haloPaint.setAlpha(HALO_ALPHA);
}
@ColorInt
private int getColorForState(@NonNull ColorStateList colorStateList) {
return colorStateList.getColorForState(getDrawableState(), colorStateList.getDefaultColor());
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SliderState sliderState = new SliderState(superState);
sliderState.valueFrom = valueFrom;
sliderState.valueTo = valueTo;
sliderState.thumbPosition = thumbPosition;
sliderState.stepSize = stepSize;
sliderState.hasFocus = hasFocus();
return sliderState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SliderState sliderState = (SliderState) state;
super.onRestoreInstanceState(sliderState.getSuperState());
valueFrom = sliderState.valueFrom;
valueTo = sliderState.valueTo;
thumbPosition = sliderState.thumbPosition;
stepSize = sliderState.stepSize;
if (sliderState.hasFocus) {
requestFocus();
}
if (hasOnChangeListener()) {
listener.onValueChange(this, getValue());
}
}
static class SliderState extends BaseSavedState {
float valueFrom;
float valueTo;
float thumbPosition;
float stepSize;
float[] ticksCoordinates;
boolean hasFocus;
public static final Parcelable.Creator