/*
 * Copyright (C) 2010 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 android.widget;

import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
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.Insets;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION_CODES;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.AllCapsTransformationMethod;
import android.text.method.TransformationMethod2;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.MathUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.android.internal.R;

A Switch is a two-state toggle switch widget that can select between two options. The user may drag the "thumb" back and forth to choose the selected option, or simply tap to toggle as if it were a checkbox. The text property controls the text displayed in the label for the switch, whereas the off and on text controls the text on the thumb. Similarly, the textAppearance and the related setTypeface() methods control the typeface and style of label text, whereas the switchTextAppearance and the related setSwitchTypeface() methods control that of the thumb.

SwitchCompat is a version of the Switch widget which runs on devices back to API 7.

See the Toggle Buttons guide.

@attrref android.R.styleable#Switch_textOn
@attrref android.R.styleable#Switch_textOff
@attrref android.R.styleable#Switch_switchMinWidth
@attrref android.R.styleable#Switch_switchPadding
@attrref android.R.styleable#Switch_switchTextAppearance
@attrref android.R.styleable#Switch_thumb
@attrref android.R.styleable#Switch_thumbTextPadding
@attrref android.R.styleable#Switch_track
/** * A Switch is a two-state toggle switch widget that can select between two * options. The user may drag the "thumb" back and forth to choose the selected option, * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text} * property controls the text displayed in the label for the switch, whereas the * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text * controls the text on the thumb. Similarly, the * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related * setTypeface() methods control the typeface and style of label text, whereas the * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and * the related setSwitchTypeface() methods control that of the thumb. * * <p>{@link android.support.v7.widget.SwitchCompat} is a version of * the Switch widget which runs on devices back to API 7.</p> * * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a> * guide.</p> * * @attr ref android.R.styleable#Switch_textOn * @attr ref android.R.styleable#Switch_textOff * @attr ref android.R.styleable#Switch_switchMinWidth * @attr ref android.R.styleable#Switch_switchPadding * @attr ref android.R.styleable#Switch_switchTextAppearance * @attr ref android.R.styleable#Switch_thumb * @attr ref android.R.styleable#Switch_thumbTextPadding * @attr ref android.R.styleable#Switch_track */
public class Switch extends CompoundButton { private static final int THUMB_ANIMATION_DURATION = 250; private static final int TOUCH_MODE_IDLE = 0; private static final int TOUCH_MODE_DOWN = 1; private static final int TOUCH_MODE_DRAGGING = 2; // Enum for the "typeface" XML parameter. private static final int SANS = 1; private static final int SERIF = 2; private static final int MONOSPACE = 3; private Drawable mThumbDrawable; private ColorStateList mThumbTintList = null; private PorterDuff.Mode mThumbTintMode = null; private boolean mHasThumbTint = false; private boolean mHasThumbTintMode = false; private Drawable mTrackDrawable; private ColorStateList mTrackTintList = null; private PorterDuff.Mode mTrackTintMode = null; private boolean mHasTrackTint = false; private boolean mHasTrackTintMode = false; private int mThumbTextPadding; private int mSwitchMinWidth; private int mSwitchPadding; private boolean mSplitTrack; private CharSequence mTextOn; private CharSequence mTextOff; private boolean mShowText; private boolean mUseFallbackLineSpacing; private int mTouchMode; private int mTouchSlop; private float mTouchX; private float mTouchY; private VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private int mMinFlingVelocity; private float mThumbPosition;
Width required to draw the switch track and thumb. Includes padding and optical bounds for both the track and thumb.
/** * Width required to draw the switch track and thumb. Includes padding and * optical bounds for both the track and thumb. */
private int mSwitchWidth;
Height required to draw the switch track and thumb. Includes padding and optical bounds for both the track and thumb.
/** * Height required to draw the switch track and thumb. Includes padding and * optical bounds for both the track and thumb. */
private int mSwitchHeight;
Width of the thumb's content region. Does not include padding or optical bounds.
/** * Width of the thumb's content region. Does not include padding or * optical bounds. */
private int mThumbWidth;
Left bound for drawing the switch track and thumb.
/** Left bound for drawing the switch track and thumb. */
private int mSwitchLeft;
Top bound for drawing the switch track and thumb.
/** Top bound for drawing the switch track and thumb. */
private int mSwitchTop;
Right bound for drawing the switch track and thumb.
/** Right bound for drawing the switch track and thumb. */
private int mSwitchRight;
Bottom bound for drawing the switch track and thumb.
/** Bottom bound for drawing the switch track and thumb. */
private int mSwitchBottom; private TextPaint mTextPaint; private ColorStateList mTextColors; private Layout mOnLayout; private Layout mOffLayout; private TransformationMethod2 mSwitchTransformationMethod; private ObjectAnimator mPositionAnimator; @SuppressWarnings("hiding") private final Rect mTempRect = new Rect(); private static final int[] CHECKED_STATE_SET = { R.attr.state_checked };
Construct a new Switch with default styling.
Params:
  • context – The Context that will determine this widget's theming.
/** * Construct a new Switch with default styling. * * @param context The Context that will determine this widget's theming. */
public Switch(Context context) { this(context, null); }
Construct a new Switch with default styling, overriding specific style attributes as requested.
Params:
  • context – The Context that will determine this widget's theming.
  • attrs – Specification of attributes that should deviate from default styling.
/** * Construct a new Switch with default styling, overriding specific style * attributes as requested. * * @param context The Context that will determine this widget's theming. * @param attrs Specification of attributes that should deviate from default styling. */
public Switch(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.switchStyle); }
Construct a new Switch with a default style determined by the given theme attribute, overriding specific style attributes as requested.
Params:
  • context – The Context that will determine this widget's theming.
  • attrs – Specification of attributes that should deviate from the default styling.
  • defStyleAttr – An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
/** * Construct a new Switch with a default style determined by the given theme attribute, * overriding specific style attributes as requested. * * @param context The Context that will determine this widget's theming. * @param attrs Specification of attributes that should deviate from the default styling. * @param defStyleAttr An attribute in the current theme that contains a * reference to a style resource that supplies default values for * the view. Can be 0 to not look for defaults. */
public Switch(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); }
Construct a new Switch with a default style determined by the given theme attribute or style resource, overriding specific style attributes as requested.
Params:
  • context – The Context that will determine this widget's theming.
  • attrs – Specification of attributes that should deviate from the default styling.
  • defStyleAttr – An attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
  • defStyleRes – A resource identifier of a style resource that supplies default values for the view, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
/** * Construct a new Switch with a default style determined by the given theme * attribute or style resource, overriding specific style attributes as * requested. * * @param context The Context that will determine this widget's theming. * @param attrs Specification of attributes that should deviate from the * default styling. * @param defStyleAttr An attribute in the current theme that contains a * reference to a style resource that supplies default values for * the view. Can be 0 to not look for defaults. * @param defStyleRes A resource identifier of a style resource that * supplies default values for the view, used only if * defStyleAttr is 0 or can not be found in the theme. Can be 0 * to not look for defaults. */
public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); final Resources res = getResources(); mTextPaint.density = res.getDisplayMetrics().density; mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale); final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes); mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb); if (mThumbDrawable != null) { mThumbDrawable.setCallback(this); } mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track); if (mTrackDrawable != null) { mTrackDrawable.setCallback(this); } mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn); mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff); mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true); mThumbTextPadding = a.getDimensionPixelSize( com.android.internal.R.styleable.Switch_thumbTextPadding, 0); mSwitchMinWidth = a.getDimensionPixelSize( com.android.internal.R.styleable.Switch_switchMinWidth, 0); mSwitchPadding = a.getDimensionPixelSize( com.android.internal.R.styleable.Switch_switchPadding, 0); mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false); mUseFallbackLineSpacing = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.P; ColorStateList thumbTintList = a.getColorStateList( com.android.internal.R.styleable.Switch_thumbTint); if (thumbTintList != null) { mThumbTintList = thumbTintList; mHasThumbTint = true; } PorterDuff.Mode thumbTintMode = Drawable.parseTintMode( a.getInt(com.android.internal.R.styleable.Switch_thumbTintMode, -1), null); if (mThumbTintMode != thumbTintMode) { mThumbTintMode = thumbTintMode; mHasThumbTintMode = true; } if (mHasThumbTint || mHasThumbTintMode) { applyThumbTint(); } ColorStateList trackTintList = a.getColorStateList( com.android.internal.R.styleable.Switch_trackTint); if (trackTintList != null) { mTrackTintList = trackTintList; mHasTrackTint = true; } PorterDuff.Mode trackTintMode = Drawable.parseTintMode( a.getInt(com.android.internal.R.styleable.Switch_trackTintMode, -1), null); if (mTrackTintMode != trackTintMode) { mTrackTintMode = trackTintMode; mHasTrackTintMode = true; } if (mHasTrackTint || mHasTrackTintMode) { applyTrackTint(); } final int appearance = a.getResourceId( com.android.internal.R.styleable.Switch_switchTextAppearance, 0); if (appearance != 0) { setSwitchTextAppearance(context, appearance); } a.recycle(); final ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledTouchSlop(); mMinFlingVelocity = config.getScaledMinimumFlingVelocity(); // Refresh display with current params refreshDrawableState(); setChecked(isChecked()); }
Sets the switch text color, size, style, hint color, and highlight color from the specified TextAppearance resource.
@attrref android.R.styleable#Switch_switchTextAppearance
/** * Sets the switch text color, size, style, hint color, and highlight color * from the specified TextAppearance resource. * * @attr ref android.R.styleable#Switch_switchTextAppearance */
public void setSwitchTextAppearance(Context context, @StyleRes int resid) { TypedArray appearance = context.obtainStyledAttributes(resid, com.android.internal.R.styleable.TextAppearance); ColorStateList colors; int ts; colors = appearance.getColorStateList(com.android.internal.R.styleable. TextAppearance_textColor); if (colors != null) { mTextColors = colors; } else { // If no color set in TextAppearance, default to the view's textColor mTextColors = getTextColors(); } ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. TextAppearance_textSize, 0); if (ts != 0) { if (ts != mTextPaint.getTextSize()) { mTextPaint.setTextSize(ts); requestLayout(); } } int typefaceIndex, styleIndex; typefaceIndex = appearance.getInt(com.android.internal.R.styleable. TextAppearance_typeface, -1); styleIndex = appearance.getInt(com.android.internal.R.styleable. TextAppearance_textStyle, -1); setSwitchTypefaceByIndex(typefaceIndex, styleIndex); boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable. TextAppearance_textAllCaps, false); if (allCaps) { mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext()); mSwitchTransformationMethod.setLengthChangesAllowed(true); } else { mSwitchTransformationMethod = null; } appearance.recycle(); } private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) { Typeface tf = null; switch (typefaceIndex) { case SANS: tf = Typeface.SANS_SERIF; break; case SERIF: tf = Typeface.SERIF; break; case MONOSPACE: tf = Typeface.MONOSPACE; break; } setSwitchTypeface(tf, styleIndex); }
Sets the typeface and style in which the text should be displayed on the switch, and turns on the fake bold and italic bits in the Paint if the Typeface that you provided does not have all the bits in the style that you specified.
/** * Sets the typeface and style in which the text should be displayed on the * switch, and turns on the fake bold and italic bits in the Paint if the * Typeface that you provided does not have all the bits in the * style that you specified. */
public void setSwitchTypeface(Typeface tf, int style) { if (style > 0) { if (tf == null) { tf = Typeface.defaultFromStyle(style); } else { tf = Typeface.create(tf, style); } setSwitchTypeface(tf); // now compute what (if any) algorithmic styling is needed int typefaceStyle = tf != null ? tf.getStyle() : 0; int need = style & ~typefaceStyle; mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); } else { mTextPaint.setFakeBoldText(false); mTextPaint.setTextSkewX(0); setSwitchTypeface(tf); } }
Sets the typeface in which the text should be displayed on the switch. Note that not all Typeface families actually have bold and italic variants, so you may need to use setSwitchTypeface(Typeface, int) to get the appearance that you actually want.
@attrref android.R.styleable#TextView_typeface
@attrref android.R.styleable#TextView_textStyle
/** * Sets the typeface in which the text should be displayed on the switch. * Note that not all Typeface families actually have bold and italic * variants, so you may need to use * {@link #setSwitchTypeface(Typeface, int)} to get the appearance * that you actually want. * * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */
public void setSwitchTypeface(Typeface tf) { if (mTextPaint.getTypeface() != tf) { mTextPaint.setTypeface(tf); requestLayout(); invalidate(); } }
Set the amount of horizontal padding between the switch and the associated text.
Params:
  • pixels – Amount of padding in pixels
@attrref android.R.styleable#Switch_switchPadding
/** * Set the amount of horizontal padding between the switch and the associated text. * * @param pixels Amount of padding in pixels * * @attr ref android.R.styleable#Switch_switchPadding */
public void setSwitchPadding(int pixels) { mSwitchPadding = pixels; requestLayout(); }
Get the amount of horizontal padding between the switch and the associated text.
Returns:Amount of padding in pixels
@attrref android.R.styleable#Switch_switchPadding
/** * Get the amount of horizontal padding between the switch and the associated text. * * @return Amount of padding in pixels * * @attr ref android.R.styleable#Switch_switchPadding */
public int getSwitchPadding() { return mSwitchPadding; }
Set the minimum width of the switch in pixels. The switch's width will be the maximum of this value and its measured width as determined by the switch drawables and text used.
Params:
  • pixels – Minimum width of the switch in pixels
@attrref android.R.styleable#Switch_switchMinWidth
/** * Set the minimum width of the switch in pixels. The switch's width will be the maximum * of this value and its measured width as determined by the switch drawables and text used. * * @param pixels Minimum width of the switch in pixels * * @attr ref android.R.styleable#Switch_switchMinWidth */
public void setSwitchMinWidth(int pixels) { mSwitchMinWidth = pixels; requestLayout(); }
Get the minimum width of the switch in pixels. The switch's width will be the maximum of this value and its measured width as determined by the switch drawables and text used.
Returns:Minimum width of the switch in pixels
@attrref android.R.styleable#Switch_switchMinWidth
/** * Get the minimum width of the switch in pixels. The switch's width will be the maximum * of this value and its measured width as determined by the switch drawables and text used. * * @return Minimum width of the switch in pixels * * @attr ref android.R.styleable#Switch_switchMinWidth */
public int getSwitchMinWidth() { return mSwitchMinWidth; }
Set the horizontal padding around the text drawn on the switch itself.
Params:
  • pixels – Horizontal padding for switch thumb text in pixels
@attrref android.R.styleable#Switch_thumbTextPadding
/** * Set the horizontal padding around the text drawn on the switch itself. * * @param pixels Horizontal padding for switch thumb text in pixels * * @attr ref android.R.styleable#Switch_thumbTextPadding */
public void setThumbTextPadding(int pixels) { mThumbTextPadding = pixels; requestLayout(); }
Get the horizontal padding around the text drawn on the switch itself.
Returns:Horizontal padding for switch thumb text in pixels
@attrref android.R.styleable#Switch_thumbTextPadding
/** * Get the horizontal padding around the text drawn on the switch itself. * * @return Horizontal padding for switch thumb text in pixels * * @attr ref android.R.styleable#Switch_thumbTextPadding */
public int getThumbTextPadding() { return mThumbTextPadding; }
Set the drawable used for the track that the switch slides within.
Params:
  • track – Track drawable
@attrref android.R.styleable#Switch_track
/** * Set the drawable used for the track that the switch slides within. * * @param track Track drawable * * @attr ref android.R.styleable#Switch_track */
public void setTrackDrawable(Drawable track) { if (mTrackDrawable != null) { mTrackDrawable.setCallback(null); } mTrackDrawable = track; if (track != null) { track.setCallback(this); } requestLayout(); }
Set the drawable used for the track that the switch slides within.
Params:
  • resId – Resource ID of a track drawable
@attrref android.R.styleable#Switch_track
/** * Set the drawable used for the track that the switch slides within. * * @param resId Resource ID of a track drawable * * @attr ref android.R.styleable#Switch_track */
public void setTrackResource(@DrawableRes int resId) { setTrackDrawable(getContext().getDrawable(resId)); }
Get the drawable used for the track that the switch slides within.
Returns:Track drawable
@attrref android.R.styleable#Switch_track
/** * Get the drawable used for the track that the switch slides within. * * @return Track drawable * * @attr ref android.R.styleable#Switch_track */
public Drawable getTrackDrawable() { return mTrackDrawable; }
Applies a tint to the track drawable. Does not modify the current tint mode, which is Mode.SRC_IN by default.

Subsequent calls to setTrackDrawable(Drawable) will automatically mutate the drawable and apply the specified tint and tint mode using Drawable.setTintList(ColorStateList).

Params:
  • tint – the tint to apply, may be null to clear tint
See Also:
@attrref android.R.styleable#Switch_trackTint
/** * Applies a tint to the track drawable. Does not modify the current * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. * <p> * Subsequent calls to {@link #setTrackDrawable(Drawable)} will * automatically mutate the drawable and apply the specified tint and tint * mode using {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#Switch_trackTint * @see #getTrackTintList() * @see Drawable#setTintList(ColorStateList) */
public void setTrackTintList(@Nullable ColorStateList tint) { mTrackTintList = tint; mHasTrackTint = true; applyTrackTint(); }
See Also:
Returns:the tint applied to the track drawable
@attrref android.R.styleable#Switch_trackTint
/** * @return the tint applied to the track drawable * @attr ref android.R.styleable#Switch_trackTint * @see #setTrackTintList(ColorStateList) */
@Nullable public ColorStateList getTrackTintList() { return mTrackTintList; }
Specifies the blending mode used to apply the tint specified by setTrackTintList(ColorStateList)} to the track drawable. The default mode is Mode.SRC_IN.
Params:
  • tintMode – the blending mode used to apply the tint, may be null to clear tint
See Also:
@attrref android.R.styleable#Switch_trackTintMode
/** * Specifies the blending mode used to apply the tint specified by * {@link #setTrackTintList(ColorStateList)}} to the track drawable. * The default mode is {@link PorterDuff.Mode#SRC_IN}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#Switch_trackTintMode * @see #getTrackTintMode() * @see Drawable#setTintMode(PorterDuff.Mode) */
public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) { mTrackTintMode = tintMode; mHasTrackTintMode = true; applyTrackTint(); }
See Also:
Returns:the blending mode used to apply the tint to the track drawable
@attrref android.R.styleable#Switch_trackTintMode
/** * @return the blending mode used to apply the tint to the track * drawable * @attr ref android.R.styleable#Switch_trackTintMode * @see #setTrackTintMode(PorterDuff.Mode) */
@Nullable public PorterDuff.Mode getTrackTintMode() { return mTrackTintMode; } private void applyTrackTint() { if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) { mTrackDrawable = mTrackDrawable.mutate(); if (mHasTrackTint) { mTrackDrawable.setTintList(mTrackTintList); } if (mHasTrackTintMode) { mTrackDrawable.setTintMode(mTrackTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (mTrackDrawable.isStateful()) { mTrackDrawable.setState(getDrawableState()); } } }
Set the drawable used for the switch "thumb" - the piece that the user can physically touch and drag along the track.
Params:
  • thumb – Thumb drawable
@attrref android.R.styleable#Switch_thumb
/** * Set the drawable used for the switch "thumb" - the piece that the user * can physically touch and drag along the track. * * @param thumb Thumb drawable * * @attr ref android.R.styleable#Switch_thumb */
public void setThumbDrawable(Drawable thumb) { if (mThumbDrawable != null) { mThumbDrawable.setCallback(null); } mThumbDrawable = thumb; if (thumb != null) { thumb.setCallback(this); } requestLayout(); }
Set the drawable used for the switch "thumb" - the piece that the user can physically touch and drag along the track.
Params:
  • resId – Resource ID of a thumb drawable
@attrref android.R.styleable#Switch_thumb
/** * Set the drawable used for the switch "thumb" - the piece that the user * can physically touch and drag along the track. * * @param resId Resource ID of a thumb drawable * * @attr ref android.R.styleable#Switch_thumb */
public void setThumbResource(@DrawableRes int resId) { setThumbDrawable(getContext().getDrawable(resId)); }
Get the drawable used for the switch "thumb" - the piece that the user can physically touch and drag along the track.
Returns:Thumb drawable
@attrref android.R.styleable#Switch_thumb
/** * Get the drawable used for the switch "thumb" - the piece that the user * can physically touch and drag along the track. * * @return Thumb drawable * * @attr ref android.R.styleable#Switch_thumb */
public Drawable getThumbDrawable() { return mThumbDrawable; }
Applies a tint to the thumb drawable. Does not modify the current tint mode, which is Mode.SRC_IN by default.

Subsequent calls to setThumbDrawable(Drawable) will automatically mutate the drawable and apply the specified tint and tint mode using Drawable.setTintList(ColorStateList).

Params:
  • tint – the tint to apply, may be null to clear tint
See Also:
@attrref android.R.styleable#Switch_thumbTint
/** * Applies a tint to the thumb drawable. Does not modify the current * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. * <p> * Subsequent calls to {@link #setThumbDrawable(Drawable)} will * automatically mutate the drawable and apply the specified tint and tint * mode using {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#Switch_thumbTint * @see #getThumbTintList() * @see Drawable#setTintList(ColorStateList) */
public void setThumbTintList(@Nullable ColorStateList tint) { mThumbTintList = tint; mHasThumbTint = true; applyThumbTint(); }
See Also:
Returns:the tint applied to the thumb drawable
@attrref android.R.styleable#Switch_thumbTint
/** * @return the tint applied to the thumb drawable * @attr ref android.R.styleable#Switch_thumbTint * @see #setThumbTintList(ColorStateList) */
@Nullable public ColorStateList getThumbTintList() { return mThumbTintList; }
Specifies the blending mode used to apply the tint specified by setThumbTintList(ColorStateList)} to the thumb drawable. The default mode is Mode.SRC_IN.
Params:
  • tintMode – the blending mode used to apply the tint, may be null to clear tint
See Also:
@attrref android.R.styleable#Switch_thumbTintMode
/** * Specifies the blending mode used to apply the tint specified by * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. * The default mode is {@link PorterDuff.Mode#SRC_IN}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#Switch_thumbTintMode * @see #getThumbTintMode() * @see Drawable#setTintMode(PorterDuff.Mode) */
public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { mThumbTintMode = tintMode; mHasThumbTintMode = true; applyThumbTint(); }
See Also:
Returns:the blending mode used to apply the tint to the thumb drawable
@attrref android.R.styleable#Switch_thumbTintMode
/** * @return the blending mode used to apply the tint to the thumb * drawable * @attr ref android.R.styleable#Switch_thumbTintMode * @see #setThumbTintMode(PorterDuff.Mode) */
@Nullable public PorterDuff.Mode getThumbTintMode() { return mThumbTintMode; } private void applyThumbTint() { if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) { mThumbDrawable = mThumbDrawable.mutate(); if (mHasThumbTint) { mThumbDrawable.setTintList(mThumbTintList); } if (mHasThumbTintMode) { mThumbDrawable.setTintMode(mThumbTintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (mThumbDrawable.isStateful()) { mThumbDrawable.setState(getDrawableState()); } } }
Specifies whether the track should be split by the thumb. When true, the thumb's optical bounds will be clipped out of the track drawable, then the thumb will be drawn into the resulting gap.
Params:
  • splitTrack – Whether the track should be split by the thumb
@attrref android.R.styleable#Switch_splitTrack
/** * Specifies whether the track should be split by the thumb. When true, * the thumb's optical bounds will be clipped out of the track drawable, * then the thumb will be drawn into the resulting gap. * * @param splitTrack Whether the track should be split by the thumb * * @attr ref android.R.styleable#Switch_splitTrack */
public void setSplitTrack(boolean splitTrack) { mSplitTrack = splitTrack; invalidate(); }
Returns whether the track should be split by the thumb.
@attrref android.R.styleable#Switch_splitTrack
/** * Returns whether the track should be split by the thumb. * * @attr ref android.R.styleable#Switch_splitTrack */
public boolean getSplitTrack() { return mSplitTrack; }
Returns the text displayed when the button is in the checked state.
@attrref android.R.styleable#Switch_textOn
/** * Returns the text displayed when the button is in the checked state. * * @attr ref android.R.styleable#Switch_textOn */
public CharSequence getTextOn() { return mTextOn; }
Sets the text displayed when the button is in the checked state.
@attrref android.R.styleable#Switch_textOn
/** * Sets the text displayed when the button is in the checked state. * * @attr ref android.R.styleable#Switch_textOn */
public void setTextOn(CharSequence textOn) { mTextOn = textOn; requestLayout(); }
Returns the text displayed when the button is not in the checked state.
@attrref android.R.styleable#Switch_textOff
/** * Returns the text displayed when the button is not in the checked state. * * @attr ref android.R.styleable#Switch_textOff */
public CharSequence getTextOff() { return mTextOff; }
Sets the text displayed when the button is not in the checked state.
@attrref android.R.styleable#Switch_textOff
/** * Sets the text displayed when the button is not in the checked state. * * @attr ref android.R.styleable#Switch_textOff */
public void setTextOff(CharSequence textOff) { mTextOff = textOff; requestLayout(); }
Sets whether the on/off text should be displayed.
Params:
  • showText – true to display on/off text
@attrref android.R.styleable#Switch_showText
/** * Sets whether the on/off text should be displayed. * * @param showText {@code true} to display on/off text * @attr ref android.R.styleable#Switch_showText */
public void setShowText(boolean showText) { if (mShowText != showText) { mShowText = showText; requestLayout(); } }
Returns:whether the on/off text should be displayed
@attrref android.R.styleable#Switch_showText
/** * @return whether the on/off text should be displayed * @attr ref android.R.styleable#Switch_showText */
public boolean getShowText() { return mShowText; } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mShowText) { if (mOnLayout == null) { mOnLayout = makeLayout(mTextOn); } if (mOffLayout == null) { mOffLayout = makeLayout(mTextOff); } } final Rect padding = mTempRect; final int thumbWidth; final int thumbHeight; if (mThumbDrawable != null) { // Cached thumb width does not include padding. mThumbDrawable.getPadding(padding); thumbWidth = mThumbDrawable.getIntrinsicWidth() - padding.left - padding.right; thumbHeight = mThumbDrawable.getIntrinsicHeight(); } else { thumbWidth = 0; thumbHeight = 0; } final int maxTextWidth; if (mShowText) { maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()) + mThumbTextPadding * 2; } else { maxTextWidth = 0; } mThumbWidth = Math.max(maxTextWidth, thumbWidth); final int trackHeight; if (mTrackDrawable != null) { mTrackDrawable.getPadding(padding); trackHeight = mTrackDrawable.getIntrinsicHeight(); } else { padding.setEmpty(); trackHeight = 0; } // Adjust left and right padding to ensure there's enough room for the // thumb's padding (when present). int paddingLeft = padding.left; int paddingRight = padding.right; if (mThumbDrawable != null) { final Insets inset = mThumbDrawable.getOpticalInsets(); paddingLeft = Math.max(paddingLeft, inset.left); paddingRight = Math.max(paddingRight, inset.right); } final int switchWidth = Math.max(mSwitchMinWidth, 2 * mThumbWidth + paddingLeft + paddingRight); final int switchHeight = Math.max(trackHeight, thumbHeight); mSwitchWidth = switchWidth; mSwitchHeight = switchHeight; super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int measuredHeight = getMeasuredHeight(); if (measuredHeight < switchHeight) { setMeasuredDimension(getMeasuredWidthAndState(), switchHeight); } }
@hide
/** @hide */
@Override public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { super.onPopulateAccessibilityEventInternal(event); final CharSequence text = isChecked() ? mTextOn : mTextOff; if (text != null) { event.getText().add(text); } } private Layout makeLayout(CharSequence text) { final CharSequence transformed = (mSwitchTransformationMethod != null) ? mSwitchTransformationMethod.getTransformation(text, this) : text; int width = (int) Math.ceil(Layout.getDesiredWidth(transformed, 0, transformed.length(), mTextPaint, getTextDirectionHeuristic())); return StaticLayout.Builder.obtain(transformed, 0, transformed.length(), mTextPaint, width) .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .build(); }
Returns:true if (x, y) is within the target area of the switch thumb
/** * @return true if (x, y) is within the target area of the switch thumb */
private boolean hitThumb(float x, float y) { if (mThumbDrawable == null) { return false; } // Relies on mTempRect, MUST be called first! final int thumbOffset = getThumbOffset(); mThumbDrawable.getPadding(mTempRect); final int thumbTop = mSwitchTop - mTouchSlop; final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop; final int thumbRight = thumbLeft + mThumbWidth + mTempRect.left + mTempRect.right + mTouchSlop; final int thumbBottom = mSwitchBottom + mTouchSlop; return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom; } @Override public boolean onTouchEvent(MotionEvent ev) { mVelocityTracker.addMovement(ev); final int action = ev.getActionMasked(); switch (action) { case MotionEvent.ACTION_DOWN: { final float x = ev.getX(); final float y = ev.getY(); if (isEnabled() && hitThumb(x, y)) { mTouchMode = TOUCH_MODE_DOWN; mTouchX = x; mTouchY = y; } break; } case MotionEvent.ACTION_MOVE: { switch (mTouchMode) { case TOUCH_MODE_IDLE: // Didn't target the thumb, treat normally. break; case TOUCH_MODE_DOWN: { final float x = ev.getX(); final float y = ev.getY(); if (Math.abs(x - mTouchX) > mTouchSlop || Math.abs(y - mTouchY) > mTouchSlop) { mTouchMode = TOUCH_MODE_DRAGGING; getParent().requestDisallowInterceptTouchEvent(true); mTouchX = x; mTouchY = y; return true; } break; } case TOUCH_MODE_DRAGGING: { final float x = ev.getX(); final int thumbScrollRange = getThumbScrollRange(); final float thumbScrollOffset = x - mTouchX; float dPos; if (thumbScrollRange != 0) { dPos = thumbScrollOffset / thumbScrollRange; } else { // If the thumb scroll range is empty, just use the // movement direction to snap on or off. dPos = thumbScrollOffset > 0 ? 1 : -1; } if (isLayoutRtl()) { dPos = -dPos; } final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1); if (newPos != mThumbPosition) { mTouchX = x; setThumbPosition(newPos); } return true; } } break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { if (mTouchMode == TOUCH_MODE_DRAGGING) { stopDrag(ev); // Allow super class to handle pressed state, etc. super.onTouchEvent(ev); return true; } mTouchMode = TOUCH_MODE_IDLE; mVelocityTracker.clear(); break; } } return super.onTouchEvent(ev); } private void cancelSuperTouch(MotionEvent ev) { MotionEvent cancel = MotionEvent.obtain(ev); cancel.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(cancel); cancel.recycle(); }
Called from onTouchEvent to end a drag operation.
Params:
  • ev – Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
/** * Called from onTouchEvent to end a drag operation. * * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL */
private void stopDrag(MotionEvent ev) { mTouchMode = TOUCH_MODE_IDLE; // Commit the change if the event is up and not canceled and the switch // has not been disabled during the drag. final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); final boolean oldState = isChecked(); final boolean newState; if (commitChange) { mVelocityTracker.computeCurrentVelocity(1000); final float xvel = mVelocityTracker.getXVelocity(); if (Math.abs(xvel) > mMinFlingVelocity) { newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0); } else { newState = getTargetCheckedState(); } } else { newState = oldState; } if (newState != oldState) { playSoundEffect(SoundEffectConstants.CLICK); } // Always call setChecked so that the thumb is moved back to the correct edge setChecked(newState); cancelSuperTouch(ev); } private void animateThumbToCheckedState(boolean newCheckedState) { final float targetPosition = newCheckedState ? 1 : 0; mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition); mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION); mPositionAnimator.setAutoCancel(true); mPositionAnimator.start(); } private void cancelPositionAnimator() { if (mPositionAnimator != null) { mPositionAnimator.cancel(); } } private boolean getTargetCheckedState() { return mThumbPosition > 0.5f; }
Sets the thumb position as a decimal value between 0 (off) and 1 (on).
Params:
  • position – new position between [0,1]
/** * Sets the thumb position as a decimal value between 0 (off) and 1 (on). * * @param position new position between [0,1] */
private void setThumbPosition(float position) { mThumbPosition = position; invalidate(); } @Override public void toggle() { setChecked(!isChecked()); } @Override public void setChecked(boolean checked) { super.setChecked(checked); // Calling the super method may result in setChecked() getting called // recursively with a different value, so load the REAL value... checked = isChecked(); if (isAttachedToWindow() && isLaidOut()) { animateThumbToCheckedState(checked); } else { // Immediately move the thumb to the new position. cancelPositionAnimator(); setThumbPosition(checked ? 1 : 0); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int opticalInsetLeft = 0; int opticalInsetRight = 0; if (mThumbDrawable != null) { final Rect trackPadding = mTempRect; if (mTrackDrawable != null) { mTrackDrawable.getPadding(trackPadding); } else { trackPadding.setEmpty(); } final Insets insets = mThumbDrawable.getOpticalInsets(); opticalInsetLeft = Math.max(0, insets.left - trackPadding.left); opticalInsetRight = Math.max(0, insets.right - trackPadding.right); } final int switchRight; final int switchLeft; if (isLayoutRtl()) { switchLeft = getPaddingLeft() + opticalInsetLeft; switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight; } else { switchRight = getWidth() - getPaddingRight() - opticalInsetRight; switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight; } final int switchTop; final int switchBottom; switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) { default: case Gravity.TOP: switchTop = getPaddingTop(); switchBottom = switchTop + mSwitchHeight; break; case Gravity.CENTER_VERTICAL: switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 - mSwitchHeight / 2; switchBottom = switchTop + mSwitchHeight; break; case Gravity.BOTTOM: switchBottom = getHeight() - getPaddingBottom(); switchTop = switchBottom - mSwitchHeight; break; } mSwitchLeft = switchLeft; mSwitchTop = switchTop; mSwitchBottom = switchBottom; mSwitchRight = switchRight; } @Override public void draw(Canvas c) { final Rect padding = mTempRect; final int switchLeft = mSwitchLeft; final int switchTop = mSwitchTop; final int switchRight = mSwitchRight; final int switchBottom = mSwitchBottom; int thumbInitialLeft = switchLeft + getThumbOffset(); final Insets thumbInsets; if (mThumbDrawable != null) { thumbInsets = mThumbDrawable.getOpticalInsets(); } else { thumbInsets = Insets.NONE; } // Layout the track. if (mTrackDrawable != null) { mTrackDrawable.getPadding(padding); // Adjust thumb position for track padding. thumbInitialLeft += padding.left; // If necessary, offset by the optical insets of the thumb asset. int trackLeft = switchLeft; int trackTop = switchTop; int trackRight = switchRight; int trackBottom = switchBottom; if (thumbInsets != Insets.NONE) { if (thumbInsets.left > padding.left) { trackLeft += thumbInsets.left - padding.left; } if (thumbInsets.top > padding.top) { trackTop += thumbInsets.top - padding.top; } if (thumbInsets.right > padding.right) { trackRight -= thumbInsets.right - padding.right; } if (thumbInsets.bottom > padding.bottom) { trackBottom -= thumbInsets.bottom - padding.bottom; } } mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom); } // Layout the thumb. if (mThumbDrawable != null) { mThumbDrawable.getPadding(padding); final int thumbLeft = thumbInitialLeft - padding.left; final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right; mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); final Drawable background = getBackground(); if (background != null) { background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom); } } // Draw the background. super.draw(c); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); final Rect padding = mTempRect; final Drawable trackDrawable = mTrackDrawable; if (trackDrawable != null) { trackDrawable.getPadding(padding); } else { padding.setEmpty(); } final int switchTop = mSwitchTop; final int switchBottom = mSwitchBottom; final int switchInnerTop = switchTop + padding.top; final int switchInnerBottom = switchBottom - padding.bottom; final Drawable thumbDrawable = mThumbDrawable; if (trackDrawable != null) { if (mSplitTrack && thumbDrawable != null) { final Insets insets = thumbDrawable.getOpticalInsets(); thumbDrawable.copyBounds(padding); padding.left += insets.left; padding.right -= insets.right; final int saveCount = canvas.save(); canvas.clipRect(padding, Op.DIFFERENCE); trackDrawable.draw(canvas); canvas.restoreToCount(saveCount); } else { trackDrawable.draw(canvas); } } final int saveCount = canvas.save(); if (thumbDrawable != null) { thumbDrawable.draw(canvas); } final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; if (switchText != null) { final int drawableState[] = getDrawableState(); if (mTextColors != null) { mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0)); } mTextPaint.drawableState = drawableState; final int cX; if (thumbDrawable != null) { final Rect bounds = thumbDrawable.getBounds(); cX = bounds.left + bounds.right; } else { cX = getWidth(); } final int left = cX / 2 - switchText.getWidth() / 2; final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2; canvas.translate(left, top); switchText.draw(canvas); } canvas.restoreToCount(saveCount); } @Override public int getCompoundPaddingLeft() { if (!isLayoutRtl()) { return super.getCompoundPaddingLeft(); } int padding = super.getCompoundPaddingLeft() + mSwitchWidth; if (!TextUtils.isEmpty(getText())) { padding += mSwitchPadding; } return padding; } @Override public int getCompoundPaddingRight() { if (isLayoutRtl()) { return super.getCompoundPaddingRight(); } int padding = super.getCompoundPaddingRight() + mSwitchWidth; if (!TextUtils.isEmpty(getText())) { padding += mSwitchPadding; } return padding; }
Translates thumb position to offset according to current RTL setting and thumb scroll range. Accounts for both track and thumb padding.
Returns:thumb offset
/** * Translates thumb position to offset according to current RTL setting and * thumb scroll range. Accounts for both track and thumb padding. * * @return thumb offset */
private int getThumbOffset() { final float thumbPosition; if (isLayoutRtl()) { thumbPosition = 1 - mThumbPosition; } else { thumbPosition = mThumbPosition; } return (int) (thumbPosition * getThumbScrollRange() + 0.5f); } private int getThumbScrollRange() { if (mTrackDrawable != null) { final Rect padding = mTempRect; mTrackDrawable.getPadding(padding); final Insets insets; if (mThumbDrawable != null) { insets = mThumbDrawable.getOpticalInsets(); } else { insets = Insets.NONE; } return mSwitchWidth - mThumbWidth - padding.left - padding.right - insets.left - insets.right; } else { return 0; } } @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (isChecked()) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); final int[] state = getDrawableState(); boolean changed = false; final Drawable thumbDrawable = mThumbDrawable; if (thumbDrawable != null && thumbDrawable.isStateful()) { changed |= thumbDrawable.setState(state); } final Drawable trackDrawable = mTrackDrawable; if (trackDrawable != null && trackDrawable.isStateful()) { changed |= trackDrawable.setState(state); } if (changed) { invalidate(); } } @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (mThumbDrawable != null) { mThumbDrawable.setHotspot(x, y); } if (mTrackDrawable != null) { mTrackDrawable.setHotspot(x, y); } } @Override protected boolean verifyDrawable(@NonNull Drawable who) { return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mThumbDrawable != null) { mThumbDrawable.jumpToCurrentState(); } if (mTrackDrawable != null) { mTrackDrawable.jumpToCurrentState(); } if (mPositionAnimator != null && mPositionAnimator.isStarted()) { mPositionAnimator.end(); mPositionAnimator = null; } } @Override public CharSequence getAccessibilityClassName() { return Switch.class.getName(); } @Override public void onProvideStructure(ViewStructure structure) { super.onProvideStructure(structure); onProvideAutoFillStructureForAssistOrAutofill(structure); } @Override public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); onProvideAutoFillStructureForAssistOrAutofill(structure); } // NOTE: currently there is no difference for Assist or AutoFill, so it doesn't take flags private void onProvideAutoFillStructureForAssistOrAutofill(ViewStructure structure) { CharSequence switchText = isChecked() ? mTextOn : mTextOff; if (!TextUtils.isEmpty(switchText)) { CharSequence oldText = structure.getText(); if (TextUtils.isEmpty(oldText)) { structure.setText(switchText); } else { StringBuilder newText = new StringBuilder(); newText.append(oldText).append(' ').append(switchText); structure.setText(newText); } // The style of the label text is provided via the base TextView class. This is more // relevant than the style of the (optional) on/off text on the switch button itself, // so ignore the size/color/style stored this.mTextPaint. } }
@hide
/** @hide */
@Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); CharSequence switchText = isChecked() ? mTextOn : mTextOff; if (!TextUtils.isEmpty(switchText)) { CharSequence oldText = info.getText(); if (TextUtils.isEmpty(oldText)) { info.setText(switchText); } else { StringBuilder newText = new StringBuilder(); newText.append(oldText).append(' ').append(switchText); info.setText(newText); } } } private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") { @Override public Float get(Switch object) { return object.mThumbPosition; } @Override public void setValue(Switch object, float value) { object.setThumbPosition(value); } }; }