java.lang.Object
↳CompoundButton
↳androidx.appcompat.widget.SwitchCompat
Gradle dependencies
compile group: 'androidx.appcompat', name: 'appcompat', version: '1.7.0'
- groupId: androidx.appcompat
 - artifactId: appcompat
 - version: 1.7.0
 
Artifact androidx.appcompat:appcompat:1.7.0 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.appcompat:appcompat com.android.support:appcompat-v7
Androidx class mapping:
androidx.appcompat.widget.SwitchCompat android.support.v7.widget.SwitchCompat
Overview
SwitchCompat is a complete backport of the core  widget that
 brings the visuals and functionality of the toggle widget to older versions
 of the Android platform. Unlike other widgets in this package, SwitchCompat
 is not automatically used in layouts that include the
 <Switch> element. Instead, you need to explicitly use
 <androidx.appcompat.widget.SwitchCompat> and the matching
 attributes in your layouts.
 
The thumb can be tinted with SwitchCompat.setThumbTintList(ColorStateList) and
 SwitchCompat.setThumbTintMode(PorterDuff.Mode) APIs as well as with the matching
 XML attributes. The track can be tinted with
 SwitchCompat.setTrackTintList(ColorStateList) and
 SwitchCompat.setTrackTintMode(PorterDuff.Mode) APIs as well as with the matching
 XML attributes.
 Supported attributes include:
 
 For more information, see the
 
 Toggle Buttons guide.
Summary
| Constructors | 
|---|
| public | SwitchCompat(Context context)
 Construct a new Switch with default styling.  | 
| public | SwitchCompat(Context context, AttributeSet attrs)
 Construct a new Switch with default styling, overriding specific style
 attributes as requested.  | 
| public | SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr)
 Construct a new Switch with a default style determined by the given theme attribute,
 overriding specific style attributes as requested.  | 
| Methods | 
|---|
| public void | draw(Canvas c)
  | 
| public void | drawableHotspotChanged(float x, float y)
  | 
| protected void | drawableStateChanged()
  | 
| public int | getCompoundPaddingLeft()
  | 
| public int | getCompoundPaddingRight()
  | 
| public ActionMode.Callback | getCustomSelectionActionModeCallback()
  | 
| public boolean | getShowText()
 Indicates whether the on/off text should be displayed.  | 
| public boolean | getSplitTrack()
 Returns whether the track should be split by the thumb.  | 
| public int | getSwitchMinWidth()
 Get the minimum width of the switch in pixels.  | 
| public int | getSwitchPadding()
 Get the amount of horizontal padding between the switch and the associated text.  | 
| public java.lang.CharSequence | getTextOff()
 Returns the text displayed when the button is not in the checked state.  | 
| public java.lang.CharSequence | getTextOn()
 Returns the text displayed when the button is in the checked state.  | 
| public Drawable | getThumbDrawable()
 Get the drawable used for the switch "thumb" - the piece that the user
 can physically touch and drag along the track.  | 
| protected final float | getThumbPosition()
  | 
| public int | getThumbTextPadding()
 Get the horizontal padding around the text drawn on the switch itself.  | 
| public ColorStateList | getThumbTintList()
  | 
| public PorterDuff.Mode | getThumbTintMode()
  | 
| public Drawable | getTrackDrawable()
 Get the drawable used for the track that the switch slides within.  | 
| public ColorStateList | getTrackTintList()
  | 
| public PorterDuff.Mode | getTrackTintMode()
  | 
| public boolean | isEmojiCompatEnabled()
  | 
| public void | jumpDrawablesToCurrentState()
  | 
| protected int[] | onCreateDrawableState(int extraSpace)
  | 
| protected void | onDraw(Canvas canvas)
  | 
| public void | onInitializeAccessibilityEvent(AccessibilityEvent event)
  | 
| public void | onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
  | 
| protected void | onLayout(boolean changed, int left, int top, int right, int bottom)
  | 
| public void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  | 
| public void | onPopulateAccessibilityEvent(AccessibilityEvent event)
  | 
| public boolean | onTouchEvent(MotionEvent ev)
  | 
| public void | setAllCaps(boolean allCaps)
  | 
| public void | setChecked(boolean checked)
  | 
| public void | setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)
 See
 TextViewCompat.setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)  | 
| public void | setEmojiCompatEnabled(boolean enabled)
  | 
| protected final void | setEnforceSwitchWidth(boolean enforceSwitchWidth)
 Sets true to enforce the switch width being at least twice of the thumb width.  | 
| public void | setFilters(InputFilter filters[])
  | 
| public void | setShowText(boolean showText)
 Sets whether the on/off text should be displayed.  | 
| public void | setSplitTrack(boolean splitTrack)
 Specifies whether the track should be split by the thumb.  | 
| public void | setSwitchMinWidth(int pixels)
 Set the minimum width of the switch in pixels.  | 
| public void | setSwitchPadding(int pixels)
 Set the amount of horizontal padding between the switch and the associated text.  | 
| public void | setSwitchTextAppearance(Context context, int resid)
 Sets the switch text color, size, style, hint color, and highlight color
 from the specified TextAppearance resource.  | 
| public void | setSwitchTypeface(Typeface typeface)
 Sets the typeface in which the text should be displayed on the switch.  | 
| public void | setSwitchTypeface(Typeface tf, int style)
 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 | setTextOff(java.lang.CharSequence textOff)
 Sets the text displayed when the button is not in the checked state.  | 
| public void | setTextOn(java.lang.CharSequence textOn)
 Sets the text displayed when the button is in the checked state.  | 
| public void | setThumbDrawable(Drawable thumb)
 Set the drawable used for the switch "thumb" - the piece that the user
 can physically touch and drag along the track.  | 
| public void | setThumbResource(int resId)
 Set the drawable used for the switch "thumb" - the piece that the user
 can physically touch and drag along the track.  | 
| public void | setThumbTextPadding(int pixels)
 Set the horizontal padding around the text drawn on the switch itself.  | 
| public void | setThumbTintList(ColorStateList tint)
 Applies a tint to the thumb drawable.  | 
| public void | setThumbTintMode(PorterDuff.Mode tintMode)
 Specifies the blending mode used to apply the tint specified by
 SwitchCompat.setThumbTintList(ColorStateList)} to the thumb drawable.  | 
| public void | setTrackDrawable(Drawable track)
 Set the drawable used for the track that the switch slides within.  | 
| public void | setTrackResource(int resId)
 Set the drawable used for the track that the switch slides within.  | 
| public void | setTrackTintList(ColorStateList tint)
 Applies a tint to the track drawable.  | 
| public void | setTrackTintMode(PorterDuff.Mode tintMode)
 Specifies the blending mode used to apply the tint specified by
 SwitchCompat.setTrackTintList(ColorStateList) to the track drawable.  | 
| public void | toggle()
  | 
| protected boolean | verifyDrawable(Drawable who)
  | 
| from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait | 
Constructors
public 
SwitchCompat(Context context)
Construct a new Switch with default styling.
Parameters:
context: The Context that will determine this widget's theming.
public 
SwitchCompat(Context context, AttributeSet attrs)
Construct a new Switch with default styling, overriding specific style
 attributes as requested.
Parameters:
context: The Context that will determine this widget's theming.
attrs: Specification of attributes that should deviate from default styling.
public 
SwitchCompat(Context context, AttributeSet attrs, int defStyleAttr)
Construct a new Switch with a default style determined by the given theme attribute,
 overriding specific style attributes as requested.
Parameters:
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.
Methods
public void 
setSwitchTextAppearance(Context context, int resid)
Sets the switch text color, size, style, hint color, and highlight color
 from the specified TextAppearance resource.
See also: 
public void 
setSwitchTypeface(Typeface tf, int style)
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 typeface)
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
 SwitchCompat.setSwitchTypeface(Typeface, int) to get the appearance
 that you actually want.
public void 
setSwitchPadding(int pixels)
Set the amount of horizontal padding between the switch and the associated text.
Parameters:
pixels: Amount of padding in pixels
See also: 
public int 
getSwitchPadding()
Get the amount of horizontal padding between the switch and the associated text.
Returns:
Amount of padding in pixels
See also: 
public void 
setSwitchMinWidth(int pixels)
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.
Parameters:
pixels: Minimum width of the switch in pixels
See also: 
public int 
getSwitchMinWidth()
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
See also: 
public void 
setThumbTextPadding(int pixels)
Set the horizontal padding around the text drawn on the switch itself.
Parameters:
pixels: Horizontal padding for switch thumb text in pixels
See also: 
public int 
getThumbTextPadding()
Get the horizontal padding around the text drawn on the switch itself.
Returns:
Horizontal padding for switch thumb text in pixels
See also: 
public void 
setTrackDrawable(Drawable track)
Set the drawable used for the track that the switch slides within.
Parameters:
track: Track drawable
See also: 
public void 
setTrackResource(int resId)
Set the drawable used for the track that the switch slides within.
Parameters:
resId: Resource ID of a track drawable
See also: 
public Drawable 
getTrackDrawable()
Get the drawable used for the track that the switch slides within.
Returns:
Track drawable
See also: 
public void 
setTrackTintList(ColorStateList tint)
Applies a tint to the track drawable. Does not modify the current
 tint mode, which is  by default.
 
 Subsequent calls to SwitchCompat.setTrackDrawable(Drawable) will
 automatically mutate the drawable and apply the specified tint and tint
 mode using DrawableCompat.setTintList(Drawable, ColorStateList).
Parameters:
tint: the tint to apply, may be null to clear tint
See also: SwitchCompat.getTrackTintList()
public ColorStateList 
getTrackTintList()
Returns:
the tint applied to the track drawable
See also: SwitchCompat.setTrackTintList(ColorStateList)
public void 
setTrackTintMode(PorterDuff.Mode tintMode)
Specifies the blending mode used to apply the tint specified by
 SwitchCompat.setTrackTintList(ColorStateList) to the track drawable.
 The default mode is .
Parameters:
tintMode: the blending mode used to apply the tint, may be
                 null to clear tint
See also: SwitchCompat.getTrackTintMode()
public PorterDuff.Mode 
getTrackTintMode()
Returns:
the blending mode used to apply the tint to the track
         drawable
See also: SwitchCompat.setTrackTintMode(PorterDuff.Mode)
public void 
setThumbDrawable(Drawable thumb)
Set the drawable used for the switch "thumb" - the piece that the user
 can physically touch and drag along the track.
Parameters:
thumb: Thumb drawable
See also: 
public void 
setThumbResource(int resId)
Set the drawable used for the switch "thumb" - the piece that the user
 can physically touch and drag along the track.
Parameters:
resId: Resource ID of a thumb drawable
See also: 
public Drawable 
getThumbDrawable()
Get the drawable used for the switch "thumb" - the piece that the user
 can physically touch and drag along the track.
Returns:
Thumb drawable
See also: 
public void 
setThumbTintList(ColorStateList tint)
Applies a tint to the thumb drawable. Does not modify the current
 tint mode, which is  by default.
 
 Subsequent calls to SwitchCompat.setThumbDrawable(Drawable) will
 automatically mutate the drawable and apply the specified tint and tint
 mode using DrawableCompat.setTintList(Drawable, ColorStateList).
Parameters:
tint: the tint to apply, may be null to clear tint
See also: SwitchCompat.getThumbTintList(), Drawable
public ColorStateList 
getThumbTintList()
Returns:
the tint applied to the thumb drawable
See also: SwitchCompat.setThumbTintList(ColorStateList)
public void 
setThumbTintMode(PorterDuff.Mode tintMode)
Specifies the blending mode used to apply the tint specified by
 SwitchCompat.setThumbTintList(ColorStateList)} to the thumb drawable.
 The default mode is .
Parameters:
tintMode: the blending mode used to apply the tint, may be
                 null to clear tint
See also: SwitchCompat.getThumbTintMode(), Drawable
public PorterDuff.Mode 
getThumbTintMode()
Returns:
the blending mode used to apply the tint to the thumb
         drawable
See also: SwitchCompat.setThumbTintMode(PorterDuff.Mode)
public void 
setSplitTrack(boolean 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.
Parameters:
splitTrack: Whether the track should be split by the thumb
See also: 
public boolean 
getSplitTrack()
Returns whether the track should be split by the thumb.
See also: 
public java.lang.CharSequence 
getTextOn()
Returns the text displayed when the button is in the checked state.
See also: 
public void 
setTextOn(java.lang.CharSequence textOn)
Sets the text displayed when the button is in the checked state.
See also: 
public java.lang.CharSequence 
getTextOff()
Returns the text displayed when the button is not in the checked state.
See also: 
public void 
setTextOff(java.lang.CharSequence textOff)
Sets the text displayed when the button is not in the checked state.
See also: 
public void 
setShowText(boolean showText)
Sets whether the on/off text should be displayed.
Parameters:
showText: true to display on/off text
See also: 
public boolean 
getShowText()
Indicates whether the on/off text should be displayed.
Returns:
true if the on/off text should be displayed, otherwise
     false
See also: 
public void 
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
public void 
onPopulateAccessibilityEvent(AccessibilityEvent event)
public boolean 
onTouchEvent(MotionEvent ev)
protected final float 
getThumbPosition()
Returns:
the current thumb position as a decimal value between 0 (off) and 1 (on).
public void 
setChecked(boolean checked)
protected void 
onLayout(boolean changed, int left, int top, int right, int bottom)
public void 
draw(Canvas c)
protected void 
onDraw(Canvas canvas)
public int 
getCompoundPaddingLeft()
public int 
getCompoundPaddingRight()
protected int[] 
onCreateDrawableState(int extraSpace)
protected void 
drawableStateChanged()
public void 
drawableHotspotChanged(float x, float y)
protected boolean 
verifyDrawable(Drawable who)
public void 
jumpDrawablesToCurrentState()
public void 
onInitializeAccessibilityEvent(AccessibilityEvent event)
public void 
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
public void 
setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)
See
 TextViewCompat.setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)
public ActionMode.Callback 
getCustomSelectionActionModeCallback()
protected final void 
setEnforceSwitchWidth(boolean enforceSwitchWidth)
Sets true to enforce the switch width being at least twice of the thumb width.
 Otherwise the switch width will be the value set by SwitchCompat.setSwitchMinWidth(int).
 The default value is true.
public void 
setAllCaps(boolean allCaps)
public void 
setFilters(InputFilter filters[])
public void 
setEmojiCompatEnabled(boolean enabled)
public boolean 
isEmojiCompatEnabled()
Source
/*
 * Copyright (C) 2014 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 androidx.appcompat.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.InputFilter;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.Property;
import android.view.ActionMode;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.R;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.text.AllCapsTransformationMethod;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.core.widget.TextViewCompat;
import androidx.emoji2.text.EmojiCompat;
import androidx.resourceinspection.annotation.Attribute;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/**
 * SwitchCompat is a complete backport of the core {@link Switch} widget that
 * brings the visuals and functionality of the toggle widget to older versions
 * of the Android platform. Unlike other widgets in this package, SwitchCompat
 * is not automatically used in layouts that include the
 * <code><Switch></code> element. Instead, you need to explicitly use
 * <code><androidx.appcompat.widget.SwitchCompat></code> and the matching
 * attributes in your layouts.
 *
 * <p>The thumb can be tinted with {@link #setThumbTintList(ColorStateList)} and
 * {@link #setThumbTintMode(PorterDuff.Mode)} APIs as well as with the matching
 * XML attributes. The track can be tinted with
 * {@link #setTrackTintList(ColorStateList)} and
 * {@link #setTrackTintMode(PorterDuff.Mode)} APIs as well as with the matching
 * XML attributes.</p>
 *
 * <p>Supported attributes include:</p>
 * <ul>
 *    <li>{@link android.R.attr#textOn}</li>
 *    <li>{@link android.R.attr#textOff}</li>
 *    <li>{@link android.R.attr#switchMinWidth}</li>
 *    <li>{@link android.R.attr#switchPadding}</li>
 *    <li>{@link android.R.attr#switchTextAppearance}</li>
 *    <li>{@link android.R.attr#thumb}</li>
 *    <li>{@link android.R.attr#thumbTextPadding}</li>
 *    <li>{@link android.R.attr#track}</li>
 *    <li>{@link android.R.attr#thumbTint}</li>
 *    <li>{@link android.R.attr#thumbTintMode}</li>
 *    <li>{@link android.R.attr#trackTint}</li>
 *    <li>{@link android.R.attr#trackTintMode}</li>
 * </ul>
 *
 * <p>For more information, see the
 * <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">
 * Toggle Buttons</a> guide.</p>
 */
public class SwitchCompat extends CompoundButton implements EmojiCompatConfigurationView {
    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;
    // We force the accessibility events to have a class name of Switch, since screen readers
    // already know how to handle their events
    private static final String ACCESSIBILITY_EVENT_CLASS_NAME = "android.widget.Switch";
    // 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 static final Property<SwitchCompat, Float> THUMB_POS =
            new Property<SwitchCompat, Float>(Float.class, "thumbPos") {
                @Override
                public Float get(SwitchCompat object) {
                    return object.mThumbPosition;
                }
                @Override
                public void set(SwitchCompat object, Float value) {
                    object.setThumbPosition(value);
                }
            };
    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 mTextOnTransformed;
    private CharSequence mTextOff;
    private CharSequence mTextOffTransformed;
    private boolean mShowText;
    private int mTouchMode;
    private int mTouchSlop;
    private float mTouchX;
    private float mTouchY;
    private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
    private int mMinFlingVelocity;
    float mThumbPosition;
    /**
     * 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.
     */
    private int mSwitchHeight;
    /**
     * 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. */
    private int mSwitchLeft;
    /** Top bound for drawing the switch track and thumb. */
    private int mSwitchTop;
    /** Right bound for drawing the switch track and thumb. */
    private int mSwitchRight;
    /** Bottom bound for drawing the switch track and thumb. */
    private int mSwitchBottom;
    private boolean mEnforceSwitchWidth = true;
    private final TextPaint mTextPaint;
    private ColorStateList mTextColors;
    private Layout mOnLayout;
    private Layout mOffLayout;
    @Nullable
    private TransformationMethod mSwitchTransformationMethod;
    ObjectAnimator mPositionAnimator;
    private final AppCompatTextHelper mTextHelper;
    @NonNull
    private AppCompatEmojiTextHelper mAppCompatEmojiTextHelper;
    @Nullable
    private EmojiCompatInitCallback mEmojiCompatInitCallback;
    @SuppressWarnings("hiding")
    private final Rect mTempRect = new Rect();
    private static final int[] CHECKED_STATE_SET = {
            android.R.attr.state_checked
    };
    /**
     * Construct a new Switch with default styling.
     *
     * @param context The Context that will determine this widget's theming.
     */
    public SwitchCompat(@NonNull Context context) {
        this(context, null);
    }
    /**
     * 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 SwitchCompat(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, R.attr.switchStyle);
    }
    /**
     * 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 SwitchCompat(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ThemeUtils.checkAppCompatTheme(this, getContext());
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        final Resources res = getResources();
        mTextPaint.density = res.getDisplayMetrics().density;
        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
                attrs, R.styleable.SwitchCompat, defStyleAttr, 0);
        ViewCompat.saveAttributeDataForStyleable(this,
                context, R.styleable.SwitchCompat, attrs,
                a.getWrappedTypeArray(), defStyleAttr, 0);
        mThumbDrawable = a.getDrawable(R.styleable.SwitchCompat_android_thumb);
        if (mThumbDrawable != null) {
            mThumbDrawable.setCallback(this);
        }
        mTrackDrawable = a.getDrawable(R.styleable.SwitchCompat_track);
        if (mTrackDrawable != null) {
            mTrackDrawable.setCallback(this);
        }
        setTextOnInternal(a.getText(R.styleable.SwitchCompat_android_textOn));
        setTextOffInternal(a.getText(R.styleable.SwitchCompat_android_textOff));
        mShowText = a.getBoolean(R.styleable.SwitchCompat_showText, true);
        mThumbTextPadding = a.getDimensionPixelSize(
                R.styleable.SwitchCompat_thumbTextPadding, 0);
        mSwitchMinWidth = a.getDimensionPixelSize(
                R.styleable.SwitchCompat_switchMinWidth, 0);
        mSwitchPadding = a.getDimensionPixelSize(
                R.styleable.SwitchCompat_switchPadding, 0);
        mSplitTrack = a.getBoolean(R.styleable.SwitchCompat_splitTrack, false);
        ColorStateList thumbTintList = a.getColorStateList(R.styleable.SwitchCompat_thumbTint);
        if (thumbTintList != null) {
            mThumbTintList = thumbTintList;
            mHasThumbTint = true;
        }
        PorterDuff.Mode thumbTintMode = DrawableUtils.parseTintMode(
                a.getInt(R.styleable.SwitchCompat_thumbTintMode, -1), null);
        if (mThumbTintMode != thumbTintMode) {
            mThumbTintMode = thumbTintMode;
            mHasThumbTintMode = true;
        }
        if (mHasThumbTint || mHasThumbTintMode) {
            applyThumbTint();
        }
        ColorStateList trackTintList = a.getColorStateList(R.styleable.SwitchCompat_trackTint);
        if (trackTintList != null) {
            mTrackTintList = trackTintList;
            mHasTrackTint = true;
        }
        PorterDuff.Mode trackTintMode = DrawableUtils.parseTintMode(
                a.getInt(R.styleable.SwitchCompat_trackTintMode, -1), null);
        if (mTrackTintMode != trackTintMode) {
            mTrackTintMode = trackTintMode;
            mHasTrackTintMode = true;
        }
        if (mHasTrackTint || mHasTrackTintMode) {
            applyTrackTint();
        }
        final int appearance = a.getResourceId(
                R.styleable.SwitchCompat_switchTextAppearance, 0);
        if (appearance != 0) {
            setSwitchTextAppearance(context, appearance);
        }
        mTextHelper = new AppCompatTextHelper(this);
        mTextHelper.loadFromAttributes(attrs, defStyleAttr);
        a.recycle();
        final ViewConfiguration config = ViewConfiguration.get(context);
        mTouchSlop = config.getScaledTouchSlop();
        mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
        AppCompatEmojiTextHelper emojiTextViewHelper = getEmojiTextViewHelper();
        emojiTextViewHelper.loadFromAttributes(attrs, defStyleAttr);
        // 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.
     *
     * @see android.R.attr#switchTextAppearance
     */
    public void setSwitchTextAppearance(Context context, int resid) {
        final TintTypedArray appearance = TintTypedArray.obtainStyledAttributes(context, resid,
                R.styleable.TextAppearance);
        ColorStateList colors;
        int ts;
        colors = appearance.getColorStateList(R.styleable.TextAppearance_android_textColor);
        if (colors != null) {
            mTextColors = colors;
        } else {
            // If no color set in TextAppearance, default to the view's textColor
            mTextColors = getTextColors();
        }
        ts = appearance.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0);
        if (ts != 0) {
            if (ts != mTextPaint.getTextSize()) {
                mTextPaint.setTextSize(ts);
                requestLayout();
            }
        }
        int typefaceIndex, styleIndex;
        typefaceIndex = appearance.getInt(R.styleable.TextAppearance_android_typeface, -1);
        styleIndex = appearance.getInt(R.styleable.TextAppearance_android_textStyle, -1);
        setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
        boolean allCaps = appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
        if (allCaps) {
            mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
        } else {
            mSwitchTransformationMethod = null;
        }
        // apply the new transform to current text
        setTextOnInternal(mTextOn);
        setTextOffInternal(mTextOff);
        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.
     */
    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
     * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
     * that you actually want.
     */
    public void setSwitchTypeface(Typeface typeface) {
        if ((mTextPaint.getTypeface() != null && !mTextPaint.getTypeface().equals(typeface))
                || (mTextPaint.getTypeface() == null && typeface != null)) {
            mTextPaint.setTypeface(typeface);
            requestLayout();
            invalidate();
        }
    }
    /**
     * Set the amount of horizontal padding between the switch and the associated text.
     *
     * @param pixels Amount of padding in pixels
     *
     * @see android.R.attr#switchPadding
     */
    public void setSwitchPadding(int pixels) {
        mSwitchPadding = pixels;
        requestLayout();
    }
    /**
     * Get the amount of horizontal padding between the switch and the associated text.
     *
     * @return Amount of padding in pixels
     *
     * @see android.R.attr#switchPadding
     */
    @Attribute("androidx.appcompat: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.
     *
     * @param pixels Minimum width of the switch in pixels
     *
     * @see android.R.attr#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.
     *
     * @return Minimum width of the switch in pixels
     *
     * @see android.R.attr#switchMinWidth
     */
    @Attribute("androidx.appcompat:switchMinWidth")
    public int getSwitchMinWidth() {
        return mSwitchMinWidth;
    }
    /**
     * Set the horizontal padding around the text drawn on the switch itself.
     *
     * @param pixels Horizontal padding for switch thumb text in pixels
     *
     * @see android.R.attr#thumbTextPadding
     */
    public void setThumbTextPadding(int pixels) {
        mThumbTextPadding = pixels;
        requestLayout();
    }
    /**
     * Get the horizontal padding around the text drawn on the switch itself.
     *
     * @return Horizontal padding for switch thumb text in pixels
     *
     * @see android.R.attr#thumbTextPadding
     */
    @Attribute("androidx.appcompat:thumbTextPadding")
    public int getThumbTextPadding() {
        return mThumbTextPadding;
    }
    /**
     * Set the drawable used for the track that the switch slides within.
     *
     * @param track Track drawable
     *
     * @see android.R.attr#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.
     *
     * @param resId Resource ID of a track drawable
     *
     * @see android.R.attr#track
     */
    public void setTrackResource(int resId) {
        setTrackDrawable(AppCompatResources.getDrawable(getContext(), resId));
    }
    /**
     * Get the drawable used for the track that the switch slides within.
     *
     * @return Track drawable
     *
     * @see android.R.attr#track
     */
    @Attribute("androidx.appcompat:track")
    public Drawable getTrackDrawable() {
        return mTrackDrawable;
    }
    /**
     * 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 DrawableCompat#setTintList(Drawable, ColorStateList)}.
     *
     * @param tint the tint to apply, may be {@code null} to clear tint
     *
     * @see #getTrackTintList()
     * @see android.R.attr#trackTint
     */
    public void setTrackTintList(@Nullable ColorStateList tint) {
        mTrackTintList = tint;
        mHasTrackTint = true;
        applyTrackTint();
    }
    /**
     * @return the tint applied to the track drawable
     *
     * @see #setTrackTintList(ColorStateList)
     * @see android.R.attr#trackTint
     */
    @Attribute("androidx.appcompat:trackTint")
    @Nullable
    public ColorStateList getTrackTintList() {
        return mTrackTintList;
    }
    /**
     * 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
     *
     * @see #getTrackTintMode()
     * @see android.R.attr#trackTintMode
     */
    public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) {
        mTrackTintMode = tintMode;
        mHasTrackTintMode = true;
        applyTrackTint();
    }
    /**
     * @return the blending mode used to apply the tint to the track
     *         drawable
     *
     * @see #setTrackTintMode(PorterDuff.Mode)
     * @see android.R.attr#trackTintMode
     */
    @Attribute("androidx.appcompat:trackTintMode")
    @Nullable
    public PorterDuff.Mode getTrackTintMode() {
        return mTrackTintMode;
    }
    private void applyTrackTint() {
        if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) {
            mTrackDrawable = DrawableCompat.wrap(mTrackDrawable).mutate();
            if (mHasTrackTint) {
                DrawableCompat.setTintList(mTrackDrawable, mTrackTintList);
            }
            if (mHasTrackTintMode) {
                DrawableCompat.setTintMode(mTrackDrawable, 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.
     *
     * @param thumb Thumb drawable
     *
     * @see android.R.attr#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.
     *
     * @param resId Resource ID of a thumb drawable
     *
     * @see android.R.attr#thumb
     */
    public void setThumbResource(int resId) {
        setThumbDrawable(AppCompatResources.getDrawable(getContext(), resId));
    }
    /**
     * Get the drawable used for the switch "thumb" - the piece that the user
     * can physically touch and drag along the track.
     *
     * @return Thumb drawable
     *
     * @see android.R.attr#thumb
     */
    @Attribute("android:thumb")
    public Drawable getThumbDrawable() {
        return mThumbDrawable;
    }
    /**
     * 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 DrawableCompat#setTintList(Drawable, ColorStateList)}.
     *
     * @param tint the tint to apply, may be {@code null} to clear tint
     *
     * @see #getThumbTintList()
     * @see android.R.attr#thumbTint
     * @see Drawable#setTintList(ColorStateList)
     */
    public void setThumbTintList(@Nullable ColorStateList tint) {
        mThumbTintList = tint;
        mHasThumbTint = true;
        applyThumbTint();
    }
    /**
     * @return the tint applied to the thumb drawable
     *
     * @see #setThumbTintList(ColorStateList)
     * @see android.R.attr#thumbTint
     */
    @Attribute("androidx.appcompat:thumbTint")
    @Nullable
    public ColorStateList getThumbTintList() {
        return mThumbTintList;
    }
    /**
     * 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
     *
     * @see #getThumbTintMode()
     * @see android.R.attr#thumbTintMode
     * @see Drawable#setTintMode(PorterDuff.Mode)
     */
    public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
        mThumbTintMode = tintMode;
        mHasThumbTintMode = true;
        applyThumbTint();
    }
    /**
     * @return the blending mode used to apply the tint to the thumb
     *         drawable
     *
     * @see #setThumbTintMode(PorterDuff.Mode)
     * @see android.R.attr#thumbTintMode
     */
    @Attribute("androidx.appcompat:thumbTintMode")
    @Nullable
    public PorterDuff.Mode getThumbTintMode() {
        return mThumbTintMode;
    }
    private void applyThumbTint() {
        if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) {
            mThumbDrawable = DrawableCompat.wrap(mThumbDrawable).mutate();
            if (mHasThumbTint) {
                DrawableCompat.setTintList(mThumbDrawable, mThumbTintList);
            }
            if (mHasThumbTintMode) {
                DrawableCompat.setTintMode(mThumbDrawable, 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.
     *
     * @param splitTrack Whether the track should be split by the thumb
     *
     * @see android.R.attr#splitTrack
     */
    public void setSplitTrack(boolean splitTrack) {
        mSplitTrack = splitTrack;
        invalidate();
    }
    /**
     * Returns whether the track should be split by the thumb.
     *
     * @see android.R.attr#splitTrack
     */
    @Attribute("androidx.appcompat:splitTrack")
    public boolean getSplitTrack() {
        return mSplitTrack;
    }
    /**
     * Returns the text displayed when the button is in the checked state.
     *
     * @see android.R.attr#textOn
     */
    @Attribute("android:textOn")
    public CharSequence getTextOn() {
        return mTextOn;
    }
    /**
     * Call this whenever setting mTextOn or mTextOnTransformed to ensure we maintain
     * consistent state
     */
    private void setTextOnInternal(CharSequence textOn) {
        mTextOn = textOn;
        mTextOnTransformed = doTransformForOnOffText(textOn);
        mOnLayout = null;
        if (mShowText) {
            setupEmojiCompatLoadCallback();
        }
    }
    /**
     * Sets the text displayed when the button is in the checked state.
     *
     * @see android.R.attr#textOn
     */
    public void setTextOn(CharSequence textOn) {
        setTextOnInternal(textOn);
        requestLayout();
        if (isChecked()) {
            // Default state is derived from on/off-text, so state has to be updated when
            // on/off-text are updated.
            setOnStateDescriptionOnRAndAbove();
        }
    }
    /**
     * Returns the text displayed when the button is not in the checked state.
     *
     * @see android.R.attr#textOff
     */
    @Attribute("android:textOff")
    public CharSequence getTextOff() {
        return mTextOff;
    }
    /**
     * Call this whenever setting mTextOff or mTextOffTransformed to ensure we maintain
     * consistent state
     */
    private void setTextOffInternal(CharSequence textOff) {
        mTextOff = textOff;
        mTextOffTransformed = doTransformForOnOffText(textOff);
        mOffLayout = null;
        if (mShowText) {
            setupEmojiCompatLoadCallback();
        }
    }
    /**
     * Sets the text displayed when the button is not in the checked state.
     *
     * @see android.R.attr#textOff
     */
    public void setTextOff(CharSequence textOff) {
        setTextOffInternal(textOff);
        requestLayout();
        if (!isChecked()) {
            // Default state is derived from on/off-text, so state has to be updated when
            // on/off-text are updated.
            setOffStateDescriptionOnRAndAbove();
        }
    }
    @Nullable
    private CharSequence doTransformForOnOffText(@Nullable CharSequence onOffText) {
        TransformationMethod transformationMethod =
                getEmojiTextViewHelper().wrapTransformationMethod(mSwitchTransformationMethod);
        return ((transformationMethod != null)
                ? transformationMethod.getTransformation(onOffText, this)
                : onOffText);
    }
    /**
     * Sets whether the on/off text should be displayed.
     *
     * @param showText {@code true} to display on/off text
     *
     * @see android.R.attr#showText
     */
    public void setShowText(boolean showText) {
        if (mShowText != showText) {
            mShowText = showText;
            requestLayout();
            if (showText) {
                setupEmojiCompatLoadCallback();
            }
        }
    }
    /**
     * Indicates whether the on/off text should be displayed.
     *
     * @return {@code true} if the on/off text should be displayed, otherwise
     *     {@code false}
     *
     * @see android.R.attr#showText
     */
    @Attribute("androidx.appcompat:showText")
    public boolean getShowText() {
        return mShowText;
    }
    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mShowText) {
            if (mOnLayout == null) {
                mOnLayout = makeLayout(mTextOnTransformed);
            }
            if (mOffLayout == null) {
                mOffLayout = makeLayout(mTextOffTransformed);
            }
        }
        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 Rect inset = DrawableUtils.getOpticalBounds(mThumbDrawable);
            paddingLeft = Math.max(paddingLeft, inset.left);
            paddingRight = Math.max(paddingRight, inset.right);
        }
        final int switchWidth =
                mEnforceSwitchWidth
                        ? Math.max(mSwitchMinWidth, 2 * mThumbWidth + paddingLeft + paddingRight)
                        : mSwitchMinWidth;
        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);
        }
    }
    @Override
    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
        super.onPopulateAccessibilityEvent(event);
        final CharSequence text = isChecked() ? mTextOn : mTextOff;
        if (text != null) {
            event.getText().add(text);
        }
    }
    private Layout makeLayout(CharSequence transformedText) {
        return new StaticLayout(transformedText, mTextPaint,
                transformedText != null
                        ? (int) Math.ceil(Layout.getDesiredWidth(transformedText, mTextPaint)) : 0,
                Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
    }
    /**
     * @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 (ViewUtils.isLayoutRtl(this)) {
                            dPos = -dPos;
                        }
                        final float newPos = 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.
     *
     * @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 = ViewUtils.isLayoutRtl(this) ? (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(final 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;
    }
    /**
     * @return the current thumb position as a decimal value between 0 (off) and 1 (on).
     */
    @FloatRange(from = 0.0, to = 1.0)
    protected final float getThumbPosition() {
        return mThumbPosition;
    }
    /**
     * Sets the thumb position as a decimal value between 0 (off) and 1 (on).
     *
     * @param position new position between [0,1]
     */
    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 (checked) {
            setOnStateDescriptionOnRAndAbove();
        } else {
            setOffStateDescriptionOnRAndAbove();
        }
        if (getWindowToken() != null && 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 Rect insets = DrawableUtils.getOpticalBounds(mThumbDrawable);
            opticalInsetLeft = Math.max(0, insets.left - trackPadding.left);
            opticalInsetRight = Math.max(0, insets.right - trackPadding.right);
        }
        final int switchRight;
        final int switchLeft;
        if (ViewUtils.isLayoutRtl(this)) {
            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(@NonNull 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 Rect thumbInsets;
        if (mThumbDrawable != null) {
            thumbInsets = DrawableUtils.getOpticalBounds(mThumbDrawable);
        } else {
            thumbInsets = DrawableUtils.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 != null) {
                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) {
                DrawableCompat.setHotspotBounds(background, 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 Rect insets = DrawableUtils.getOpticalBounds(thumbDrawable);
                thumbDrawable.copyBounds(padding);
                padding.left += insets.left;
                padding.right -= insets.right;
                final int saveCount = canvas.save();
                canvas.clipRect(padding, Region.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 (!ViewUtils.isLayoutRtl(this)) {
            return super.getCompoundPaddingLeft();
        }
        int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
        if (!TextUtils.isEmpty(getText())) {
            padding += mSwitchPadding;
        }
        return padding;
    }
    @Override
    public int getCompoundPaddingRight() {
        if (ViewUtils.isLayoutRtl(this)) {
            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.
     *
     * @return thumb offset
     */
    private int getThumbOffset() {
        final float thumbPosition;
        if (ViewUtils.isLayoutRtl(this)) {
            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 Rect insets;
            if (mThumbDrawable != null) {
                insets = DrawableUtils.getOpticalBounds(mThumbDrawable);
            } else {
                insets = DrawableUtils.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) {
        if (Build.VERSION.SDK_INT >= 21) {
            super.drawableHotspotChanged(x, y);
        }
        if (mThumbDrawable != null) {
            DrawableCompat.setHotspot(mThumbDrawable, x, y);
        }
        if (mTrackDrawable != null) {
            DrawableCompat.setHotspot(mTrackDrawable, 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 void onInitializeAccessibilityEvent(AccessibilityEvent event) {
        super.onInitializeAccessibilityEvent(event);
        event.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME);
    }
    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(ACCESSIBILITY_EVENT_CLASS_NAME);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
            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);
                }
            }
        }
    }
    /**
     * See
     * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
     */
    @Override
    public void setCustomSelectionActionModeCallback(
            @Nullable ActionMode.Callback actionModeCallback) {
        super.setCustomSelectionActionModeCallback(
                TextViewCompat.wrapCustomSelectionActionModeCallback(this, actionModeCallback));
    }
    @Override
    @Nullable
    public ActionMode.Callback getCustomSelectionActionModeCallback() {
        return TextViewCompat.unwrapCustomSelectionActionModeCallback(
                super.getCustomSelectionActionModeCallback());
    }
    /**
     * Sets {@code true} to enforce the switch width being at least twice of the thumb width.
     * Otherwise the switch width will be the value set by {@link #setSwitchMinWidth(int)}.
     *
     * The default value is {@code true}.
     */
    protected final void setEnforceSwitchWidth(boolean enforceSwitchWidth) {
        mEnforceSwitchWidth = enforceSwitchWidth;
        invalidate();
    }
    /**
     * Taken from android.util.MathUtils
     */
    private static float constrain(float amount, float low, float high) {
        return amount < low ? low : (amount > high ? high : amount);
    }
    private void setOnStateDescriptionOnRAndAbove() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            ViewCompat.setStateDescription(
                    this,
                    mTextOn == null ? getResources().getString(R.string.abc_capital_on) : mTextOn
            );
        }
    }
    private void setOffStateDescriptionOnRAndAbove() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            ViewCompat.setStateDescription(
                    this,
                    mTextOff == null ? getResources().getString(R.string.abc_capital_off) : mTextOff
            );
        }
    }
    @Override
    public void setAllCaps(boolean allCaps) {
        super.setAllCaps(allCaps);
        getEmojiTextViewHelper().setAllCaps(allCaps);
    }
    @Override
    public void setFilters(@SuppressWarnings("ArrayReturn") @NonNull InputFilter[] filters) {
        super.setFilters(getEmojiTextViewHelper().getFilters(filters));
    }
    /**
     * This may be called from super constructors.
     */
    @NonNull
    private AppCompatEmojiTextHelper getEmojiTextViewHelper() {
        //noinspection ConstantConditions
        if (mAppCompatEmojiTextHelper == null) {
            mAppCompatEmojiTextHelper = new AppCompatEmojiTextHelper(this);
        }
        return mAppCompatEmojiTextHelper;
    }
    @Override
    public void setEmojiCompatEnabled(boolean enabled) {
        getEmojiTextViewHelper().setEnabled(enabled);
        // the transformation method may have changed for on/off text so call again
        setTextOnInternal(mTextOn);
        setTextOffInternal(mTextOff);
        requestLayout();
    }
    @Override
    public boolean isEmojiCompatEnabled() {
        return getEmojiTextViewHelper().isEnabled();
    }
    /**
     * Call this before caching the text in mOnLayout or mOffLayout to ensure the layouts get
     * updated when emojicompat loads
     */
    private void setupEmojiCompatLoadCallback() {
        // Note: This is called again from onEmojiCompatInitializedForSwitchText, do not remove
        // null check of mEmojiCompatInitCallback without refactoring.
        if (mEmojiCompatInitCallback != null || !mAppCompatEmojiTextHelper.isEnabled()) {
            return;
        }
        if (EmojiCompat.isConfigured()) {
            EmojiCompat emojiCompat = EmojiCompat.get();
            int loadState = emojiCompat.getLoadState();
            if (loadState == EmojiCompat.LOAD_STATE_DEFAULT
                    || loadState == EmojiCompat.LOAD_STATE_LOADING) {
                // we can eventually load from default and loading
                mEmojiCompatInitCallback = new EmojiCompatInitCallback(this);
                emojiCompat.registerInitCallback(mEmojiCompatInitCallback);
            }
        }
    }
    /**
     * Update cached transformed text in mTextOn and mTextOff
     */
    void onEmojiCompatInitializedForSwitchText() {
        // this is required since we manage our own transformation method in this class during
        // setTextOn and setTextOff
        // if makeLayout, mOnLayout, or mOffLayout are removed, this can likely be removed
        setTextOnInternal(mTextOn);
        setTextOffInternal(mTextOff);
        requestLayout();
    }
    static class EmojiCompatInitCallback extends EmojiCompat.InitCallback {
        private final Reference<SwitchCompat> mOuterWeakRef;
        EmojiCompatInitCallback(SwitchCompat view) {
            mOuterWeakRef = new WeakReference<>(view);
        }
        @Override
        public void onInitialized() {
            SwitchCompat view = mOuterWeakRef.get();
            if (view != null) {
                view.onEmojiCompatInitializedForSwitchText();
            }
        }
        @Override
        public void onFailed(@Nullable Throwable throwable) {
            SwitchCompat view = mOuterWeakRef.get();
            if (view != null) {
                view.onEmojiCompatInitializedForSwitchText();
            }
        }
    }
}