package com.tencent.tws.assistant.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.CompoundButton;
import com.tencent.tws.assistant.drawable.TwsRippleDrawable;
import com.tencent.tws.assistant.support.v4.graphics.drawable.DrawableCompat;
import com.tencent.tws.assistant.utils.FloatProperty;
import com.tencent.tws.assistant.utils.MathUtils;
import com.tencent.tws.assistant.utils.ThemeUtils;
import com.tencent.tws.sharelib.R;
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;
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;
//alpha effect
private Drawable mOverlayThumbDrawable;
private Drawable mOverlayTrackDrawable;
private int mOverlayAlpha;
private int mThumbTextPadding;
private int mSwitchMinWidth;
private int mSwitchPadding;
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.
*/
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 ObjectAnimator mPositionAnimator;
@SuppressWarnings("hiding")
private final Rect mTempRect = new Rect();
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
private TintManager mTintManager;
private boolean mIsSupportTintDrawable;
private int mNormalColor = 0;
private int mCheckedColor = 0;
private int mDisabledColor = 0;
private int mCheckedDisableColor = 0;
private int mCurrentColor = 0;
/**
* 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.
*
* @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, 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 Switch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// mIsSupportTintDrawable = ThemeUtils.isSupportTintDrawable(context);
mIsSupportTintDrawable = true;
// final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.Switch,
// defStyleAttr, 0);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch,
defStyleAttr, 0);
if (mIsSupportTintDrawable) {
mThumbDrawable = getResources().getDrawable(R.drawable.switch_frame_levellist);
if (mThumbDrawable != null) {
mThumbDrawable.setCallback(this);
}
} else {
mThumbDrawable = a.getDrawable(R.styleable.Switch_thumb);
if (mThumbDrawable != null) {
mThumbDrawable.setCallback(this);
}
}
if (!mIsSupportTintDrawable) {
// mOverlayThumbDrawable = a.getDrawable(R.styleable.Switch_thumbOverLayer);
if (mOverlayThumbDrawable != null) {
mOverlayThumbDrawable.setCallback(this);
}
}
mOverlayAlpha = 0;
mNormalColor = ThemeUtils.getThemeAttrColor(getContext(), R.attr.colorControlNormal);
mCheckedColor = ThemeUtils.getThemeAttrColor(getContext(), R.attr.colorControlActivated);
mDisabledColor = ThemeUtils.getDisabledThemeAttrColor(getContext(), R.attr.colorControlNormal);
mCheckedDisableColor = ThemeUtils.getDisabledThemeAttrColor(getContext(), R.attr.colorControlActivated);
if (mNormalColor == 0) {
mNormalColor = getContext().getResources().getColor(R.color.control_normal_color);
}
if (mCheckedColor == 0) {
mCheckedColor = getContext().getResources().getColor(R.color.control_activated_color);
}
if (mDisabledColor == 0) {
mDisabledColor = getContext().getResources().getColor(R.color.control_disabled_color);
}
if (mCheckedDisableColor == 0) {
mCheckedDisableColor = getContext().getResources().getColor(R.color.control_activate_disabled_color);
}
// mTrackDrawable = a.getDrawable(R.styleable.Switch_track);
if (mTrackDrawable != null) {
mTrackDrawable.setCallback(this);
}
if (!mIsSupportTintDrawable) {
mOverlayTrackDrawable = a.getDrawable(R.styleable.Switch_trackOverLayer);
if (mOverlayTrackDrawable != null) {
mOverlayTrackDrawable.setCallback(this);
}
}
updateCurrentColor(isChecked(), isEnabled());
setRippleBackground(mIsSupportTintDrawable, mTrackDrawable, mCheckedColor);
mSwitchMinWidth = a.getDimensionPixelSize(R.styleable.Switch_switchMinWidth, 0);
mSwitchPadding = a.getDimensionPixelSize(R.styleable.Switch_switchPadding, 0);
ColorStateList thumbTintList = a.getColorStateList(R.styleable.Switch_thumbTint);
if (thumbTintList != null) {
mThumbTintList = thumbTintList;
mHasThumbTint = true;
}
PorterDuff.Mode thumbTintMode = TintManager.parseTintMode(
a.getInt(R.styleable.Switch_thumbTintMode, -1), null);
if (mThumbTintMode != thumbTintMode) {
mThumbTintMode = thumbTintMode;
mHasThumbTintMode = true;
}
if (mHasThumbTint || mHasThumbTintMode) {
applyThumbTint();
}
ColorStateList trackTintList = a.getColorStateList(R.styleable.Switch_trackTint);
if (trackTintList != null) {
mTrackTintList = trackTintList;
mHasTrackTint = true;
}
PorterDuff.Mode trackTintMode = TintManager.parseTintMode(
a.getInt(R.styleable.Switch_trackTintMode, -1), null);
if (mTrackTintMode != trackTintMode) {
mTrackTintMode = trackTintMode;
mHasTrackTintMode = true;
}
if (mHasTrackTint || mHasTrackTintMode) {
applyTrackTint();
}
// mTintManager = a.getTintManager();
a.recycle();
final ViewConfiguration config = ViewConfiguration.get(context);
mTouchSlop = config.getScaledTouchSlop();
mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
// Refresh display with current params
refreshDrawableState();
setChecked(isChecked());
}
/**
* 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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @param resId
* Resource ID of a track drawable
*
* @attr ref android.R.styleable#Switch_track
*/
public void setTrackResource(int resId) {
Drawable d = null;
if (mTintManager != null) {
d = mTintManager.getDrawable(resId);
} else {
d = getContext().getResources().getDrawable(resId);
}
setTrackDrawable(d);
}
/**
* 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
* {@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(ColorStateList tint) {
mTrackTintList = tint;
mHasTrackTint = true;
applyTrackTint();
}
/**
* @return the tint applied to the track drawable
* @attr ref android.R.styleable#Switch_trackTint
* @see #setTrackTintList(ColorStateList)
*/
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
* @attr ref android.R.styleable#Switch_trackTintMode
* @see #getTrackTintMode()
* @see Drawable#setTintMode(PorterDuff.Mode)
*/
public void setTrackTintMode(PorterDuff.Mode tintMode) {
mTrackTintMode = tintMode;
mHasTrackTintMode = true;
applyTrackTint();
}
/**
* @return the blending mode used to apply the tint to the track drawable
* @attr ref android.R.styleable#Switch_trackTintMode
* @see #setTrackTintMode(PorterDuff.Mode)
*/
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);
// }
TintInfo tintInfo = new TintInfo();
tintInfo.mHasTintList = mHasTrackTint;
tintInfo.mHasTintMode = mHasTrackTintMode;
tintInfo.mTintList = mTrackTintList;
tintInfo.mTintMode = mTrackTintMode;
TintManager.tintDrawable(mTrackDrawable, tintInfo, getDrawableState());
// 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
*
* @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.
*
* @param resId
* Resource ID of a thumb drawable
*
* @attr ref android.R.styleable#Switch_thumb
*/
public void setThumbResource(int resId) {
Drawable d = null;
if (mTintManager != null) {
d = mTintManager.getDrawable(resId);
} else {
d = getContext().getResources().getDrawable(resId);
}
setThumbDrawable(d);
}
/**
* 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
* {@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(ColorStateList tint) {
mThumbTintList = tint;
mHasThumbTint = true;
applyThumbTint();
}
/**
* @return the tint applied to the thumb drawable
* @attr ref android.R.styleable#Switch_thumbTint
* @see #setThumbTintList(ColorStateList)
*/
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
* @attr ref android.R.styleable#Switch_thumbTintMode
* @see #getThumbTintMode()
* @see Drawable#setTintMode(PorterDuff.Mode)
*/
public void setThumbTintMode(PorterDuff.Mode tintMode) {
mThumbTintMode = tintMode;
mHasThumbTintMode = true;
applyThumbTint();
}
/**
* @return the blending mode used to apply the tint to the thumb drawable
* @attr ref android.R.styleable#Switch_thumbTintMode
* @see #setThumbTintMode(PorterDuff.Mode)
*/
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);
// }
TintInfo tintInfo = new TintInfo();
tintInfo.mHasTintList = mHasThumbTint;
tintInfo.mHasTintMode = mHasThumbTintMode;
tintInfo.mTintList = mThumbTintList;
tintInfo.mTintMode = mThumbTintMode;
TintManager.tintDrawable(mThumbDrawable, tintInfo, getDrawableState());
mThumbDrawable.invalidateSelf();
// 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());
}
}
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
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;
}
mThumbWidth = Math.max(0, thumbWidth);
final int trackHeight;
final int trackWidth;
if (mTrackDrawable != null) {
mTrackDrawable.getPadding(padding);
trackHeight = mTrackDrawable.getIntrinsicHeight();
trackWidth = mTrackDrawable.getIntrinsicWidth();
} else {
padding.setEmpty();
trackHeight = 0;
trackWidth = 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);
}
int switchWidth = Math.max(mSwitchMinWidth,
2 * mThumbWidth + paddingLeft + paddingRight);
if (mIsSupportTintDrawable) {
switchWidth = Math.max(mThumbWidth, trackWidth);
}
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);
}
}
/**
* @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;
int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop;
if (mIsSupportTintDrawable) {
thumbLeft = mSwitchLeft - mTouchSlop;
}
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.
*
* @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);
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);
if (android.os.Build.VERSION.SDK_INT > 17) {
mPositionAnimator.setAutoCancel(true);
}
if (mIsSupportTintDrawable) {
final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
startCheckedRipple(newCheckedState);
}
};
mPositionAnimator.addListener(animatorListener);
}
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).
*
* @param position
* new position between [0,1]
*/
private void setThumbPosition(float position) {
mThumbPosition = position;
if (mIsSupportTintDrawable) {
ArgbEvaluator evaluator = new ArgbEvaluator();
if (isEnabled()) {
mCurrentColor = (Integer) (evaluator.evaluate(position, mNormalColor, mCheckedColor));
} else {
mCurrentColor = (Integer) (evaluator.evaluate(position, mDisabledColor, mCheckedDisableColor));
}
int index = (int) (mThumbPosition * 10);
if (mThumbDrawable != null) {
mThumbDrawable.setLevel(index);
}
if (mThumbDrawable != null) {
// mThumbDrawable.setColorFilter(mCurrentColor, PorterDuff.Mode.SRC_IN);
}
if (mTrackDrawable != null) {
// mTrackDrawable.setColorFilter(mCurrentColor, PorterDuff.Mode.SRC_IN);
}
}
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 (getWindowToken() != null && isLaidOut(this) && isShown()) {
if (android.os.Build.VERSION.SDK_INT > 18 && isAttachedToWindow() && isLaidOut()) {
animateThumbToCheckedState(checked);
} else {
// Immediately move the thumb to the new position.
cancelPositionAnimator();
setThumbPosition(checked ? 1 : 0);
startCheckedRipple(checked);
}
}
@Deprecated
public void setCheckedAnima(boolean checked) {
}
@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 (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 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;
//TODO
if (mIsSupportTintDrawable) {
trackRight = mTrackDrawable.getIntrinsicWidth();
}
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 track overlayer.
if (mOverlayTrackDrawable != null) {
mOverlayTrackDrawable.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 = (int) (switchRight * mThumbPosition);
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;
}
}
mOverlayTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom);
}
// Layout the thumb.
if (mThumbDrawable != null) {
mThumbDrawable.getPadding(padding);
int thumbLeft = thumbInitialLeft - padding.left;
int thumbRight = thumbInitialLeft + mThumbWidth + padding.right;
if (mIsSupportTintDrawable) {
thumbLeft = switchLeft - padding.left;
thumbRight = mThumbWidth + padding.right;
}
mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
final Drawable background = getBackground();
if (background != null) {
DrawableCompat.setHotspotBounds(background, thumbLeft, switchTop,
thumbRight, switchBottom);
}
}
// Layout the thumb overlayer.
if (mOverlayThumbDrawable != null) {
mOverlayThumbDrawable.getPadding(padding);
final int thumbLeft = thumbInitialLeft - padding.left;
final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right;
mOverlayThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
}
// Draw the background.
super.draw(c);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mOverlayAlpha = calculateAlpha();
final Drawable overlayThumbDrawable = mOverlayThumbDrawable;
final Drawable overlayTrackDrawable = mOverlayTrackDrawable;
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) {
trackDrawable.draw(canvas);
}
if (overlayTrackDrawable != null) {
overlayTrackDrawable.draw(canvas);
}
final int saveCount = canvas.save();
if (thumbDrawable != null) {
thumbDrawable.draw(canvas);
}
if (overlayThumbDrawable != null) {
overlayThumbDrawable.setAlpha(mOverlayAlpha);
overlayThumbDrawable.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.
*
* @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 Rect insets;
if (mThumbDrawable != null) {
insets = DrawableUtils.getOpticalBounds(mThumbDrawable);
} else {
insets = DrawableUtils.INSETS_NONE;
}
if (mIsSupportTintDrawable) {
return mSwitchWidth - padding.left - padding.right
- insets.left - insets.right;
}
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[] myDrawableState = getDrawableState();
if (mThumbDrawable != null) {
mThumbDrawable.setState(myDrawableState);
}
if (mTrackDrawable != null) {
mTrackDrawable.setState(myDrawableState);
}
if (mOverlayThumbDrawable != null) {
mOverlayThumbDrawable.setState(myDrawableState);
}
if (mOverlayTrackDrawable != null) {
mOverlayTrackDrawable.setState(myDrawableState);
}
updateCurrentColor(isChecked(), isEnabled());
invalidate();
}
/*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(Drawable who) {
return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable
|| who == mOverlayThumbDrawable || who == mOverlayTrackDrawable;
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mThumbDrawable != null) {
mThumbDrawable.jumpToCurrentState();
}
if (mTrackDrawable != null) {
mTrackDrawable.jumpToCurrentState();
}
if (mOverlayThumbDrawable != null) {
mOverlayThumbDrawable.jumpToCurrentState();
}
if (mOverlayTrackDrawable != null) {
mOverlayTrackDrawable.jumpToCurrentState();
}
if (mPositionAnimator != null && mPositionAnimator.isRunning()) {
mPositionAnimator.end();
mPositionAnimator = null;
}
}
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);
}
};
private boolean isLaidOut(View view){
return view.getWidth() > 0 && view.getHeight() > 0;
}
private int calculateAlpha() {
int alpha = (int) (mThumbPosition * 255);
if (alpha <= 0) {
alpha = 0;
} else if (alpha >= 255) {
alpha = 255;
}
return alpha;
}
private void updateCurrentColor(boolean checked, boolean enabled) {
if (mIsSupportTintDrawable) {
if (checked) {
if (enabled) {
mCurrentColor = mCheckedColor;
} else {
mCurrentColor = mCheckedDisableColor;
}
} else {
if (enabled) {
mCurrentColor = mNormalColor;
} else {
mCurrentColor = mDisabledColor;
}
}
if (mThumbDrawable != null) {
// mThumbDrawable.setColorFilter(mCurrentColor, PorterDuff.Mode.SRC_IN);
}
if (mTrackDrawable != null) {
// mTrackDrawable.setColorFilter(mCurrentColor, PorterDuff.Mode.SRC_IN);
}
}
}
private void setRippleBackground(boolean isSupportTintDrawable, Drawable orgDrawable, int rippleColor) {
if (orgDrawable != null && isSupportTintDrawable) {
ColorStateList csl = createNormalStateList(rippleColor);
final float scale = 0.3f;
int width = orgDrawable.getIntrinsicWidth();
int height = orgDrawable.getIntrinsicHeight();
int left = (int) (width - (height * (1 + scale)));
int top = (int) (-height * scale);
int right = (int) (width + height * scale);
int bottom = height - top;
TwsRippleDrawable rippleDrawable = new TwsRippleDrawable(csl, null, null,
TwsRippleDrawable.RIPPLE_STYLE_RING);
rippleDrawable.twsSetHotspotBounds(left, top, right, bottom);
rippleDrawable.setSupportCheckedRipple(false);
setBackgroundDrawable(rippleDrawable);
}
}
private ColorStateList createNormalStateList(int textColor) {
// Now create a new ColorStateList with the default color
final int[][] states = new int[1][];
final int[] colors = new int[1];
// Default state
states[0] = EMPTY_STATE_SET;
colors[0] = textColor;
return new ColorStateList(states, colors);
}
private void startCheckedRipple(boolean newCheckedState){
final Drawable drawable = getBackground();
if (drawable instanceof TwsRippleDrawable) {
((TwsRippleDrawable) drawable).startCheckedRipple(newCheckedState);
}
}
}