Back to project page L-Switch-backport.
The source code is released under:
Apache License
If you think the Android project L-Switch-backport listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package me.yugy.github.lswitchbackport.library; //ww w .j ava2 s . co m import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.os.Handler; import android.os.Message; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.widget.CompoundButton; /** * Created by yugy on 2014/7/16. */ public class Switch extends CompoundButton { 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 static final int MESSAGE_ANIMATE_TO_ON = 0; private static final int MESSAGE_ANIMATE_TO_OFF = 1; private Drawable mThumbDrawable; private Drawable mTrackDrawable; private final int mSwitchMinWidth; private final int mSwitchPadding; private int mTouchMode; private final int mTouchSlop; private float mTouchX; private float mTouchY; private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private final int mMinFlingVelocity; private float mThumbPosition; private int mSwitchWidth; private int mSwitchHeight; private int mThumbWidth; // Does not include padding private int mThumbAnimateOffset; private int mSwitchLeft; private int mSwitchTop; private int mSwitchRight; private int mSwitchBottom; private final Rect mTempRect = new Rect(); private Handler mHandler; 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 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 defStyle An attribute ID within the active theme containing a reference to the * default style for this widget. e.g. android.R.attr.switchStyle. */ public Switch(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch, defStyle, 0); mThumbDrawable = a.getDrawable(R.styleable.Switch_thumb); mTrackDrawable = a.getDrawable(R.styleable.Switch_track); mSwitchMinWidth = a.getDimensionPixelSize(R.styleable.Switch_switchMinWidth, 48); mSwitchPadding = a.getDimensionPixelSize(R.styleable.Switch_switchPadding, 16); mThumbWidth = a.getDimensionPixelSize(R.styleable.Switch_thumbWidth, 28); mThumbAnimateOffset = a.getDimensionPixelSize(R.styleable.Switch_thumbAnimateOffset, 4); a.recycle(); if(mThumbDrawable == null){ mThumbDrawable = context.getResources().getDrawable(R.drawable.switch_inner); } if(mTrackDrawable == null){ mTrackDrawable = context.getResources().getDrawable(R.drawable.switch_track_mtrl_alpha); } final ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledTouchSlop(); mMinFlingVelocity = config.getScaledMinimumFlingVelocity(); // Refresh display with current params refreshDrawableState(); setChecked(isChecked()); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.what){ case MESSAGE_ANIMATE_TO_OFF: mThumbPosition -= mThumbAnimateOffset; checkThumbPosition(); invalidate(); return true; case MESSAGE_ANIMATE_TO_ON: mThumbPosition += mThumbAnimateOffset; checkThumbPosition(); invalidate(); return true; default: return false; } } }); } private void checkThumbPosition(){ if(mThumbPosition < 0){ mThumbPosition = 0; }else if(mThumbPosition > getThumbScrollRange()){ mThumbPosition = getThumbScrollRange(); } } @Override @SuppressLint("NewApi") public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mTrackDrawable.getPadding(mTempRect); final int switchWidth = Math.max(mSwitchMinWidth, 2 * mThumbWidth + mTempRect.left + mTempRect.right); final int switchHeight = mThumbDrawable.getIntrinsicHeight(); mSwitchWidth = mSwitchMinWidth; mSwitchHeight = switchHeight; super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int measuredHeight = getMeasuredHeight(); if (measuredHeight < switchHeight) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { setMeasuredDimension(getMeasuredWidth(), switchHeight); } else { setMeasuredDimension(getMeasuredWidthAndState(), switchHeight); } } } /** * @return true if (x, y) is within the target area of the switch thumb */ private boolean hitThumb(float x, float y) { mThumbDrawable.getPadding(mTempRect); final int thumbTop = mSwitchTop - mTouchSlop; final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - 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; } @SuppressLint("NewApi") @Override public boolean onTouchEvent(MotionEvent ev) { mVelocityTracker.addMovement(ev); final int action; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) action = ev.getAction(); else 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 float dx = x - mTouchX; final float newPos = Math.max(0, Math.min(mThumbPosition + dx, getThumbScrollRange())); if (newPos != mThumbPosition) { mThumbPosition = newPos; mTouchX = x; invalidate(); } return true; } } break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { if (mTouchMode == TOUCH_MODE_DRAGGING) { stopDrag(ev); return true; } mTouchMode = TOUCH_MODE_IDLE; mVelocityTracker.clear(); break; } } return super.onTouchEvent(ev); } private void cancelSuperTouch(MotionEvent ev) { final 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; // Up and not canceled, also checks the switch has not been disabled during the drag final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); cancelSuperTouch(ev); if (commitChange) { boolean newState; mVelocityTracker.computeCurrentVelocity(1000); final float xvel = mVelocityTracker.getXVelocity(); if (Math.abs(xvel) > mMinFlingVelocity) { newState = xvel > 0; } else { newState = getTargetCheckedState(); } animateThumbToCheckedState(newState); } else { animateThumbToCheckedState(isChecked()); } } private void animateThumbToCheckedState(boolean newCheckedState) { // TODO animate! setChecked(newCheckedState); } private boolean getTargetCheckedState() { return mThumbPosition >= getThumbScrollRange() / 2; } @Override public void setChecked(boolean checked) { super.setChecked(checked); // mThumbPosition = checked ? getThumbScrollRange() : 0; invalidate(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mThumbPosition = isChecked() ? getThumbScrollRange() : 0; final int switchRight = getWidth() - getPaddingRight(); final int switchLeft = switchRight - mSwitchWidth; int switchTop = 0; int switchBottom = 0; switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) { 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 protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the switch final int switchLeft = mSwitchLeft; final int switchTop = mSwitchTop; final int switchRight = mSwitchRight; final int switchBottom = mSwitchBottom; mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom); mTrackDrawable.draw(canvas); canvas.save(); mTrackDrawable.getPadding(mTempRect); final int switchInnerLeft = switchLeft + mTempRect.left; final int switchInnerRight = switchRight - mTempRect.right; canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom); mThumbDrawable.getPadding(mTempRect); final int thumbPos = (int) (mThumbPosition + 0.5f); final int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos; final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right; mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); mThumbDrawable.draw(canvas); canvas.restore(); if(mTouchMode == TOUCH_MODE_IDLE){ if(mThumbPosition != 0 || mThumbPosition != getThumbScrollRange()){ if(isChecked()){ mHandler.sendEmptyMessageDelayed(MESSAGE_ANIMATE_TO_ON, 12); }else{ mHandler.sendEmptyMessageDelayed(MESSAGE_ANIMATE_TO_OFF, 12); } } } } @Override public int getCompoundPaddingRight() { int padding = super.getCompoundPaddingRight() + mSwitchWidth; if (!TextUtils.isEmpty(getText())) { padding += mSwitchPadding; } return padding; } private int getThumbScrollRange() { if (mTrackDrawable == null) { return 0; } mTrackDrawable.getPadding(mTempRect); return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right; } @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(); // Set the state of the Drawable // Drawable may be null when checked state is set from XML, from super constructor if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState); if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState); invalidate(); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; } // XXX Was on the Android source, but had to comment it out (doesn't exist in 2.1). -- BoD // @Override // public void jumpDrawablesToCurrentState() { // super.jumpDrawablesToCurrentState(); // mThumbDrawable.jumpToCurrentState(); // mTrackDrawable.jumpToCurrentState(); // } }