import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.RadialGradient; import android.graphics.RectF; import android.graphics.Shader; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.animation.AccelerateInterpolator; public class SwitchView extends View{ private final Paint paint = new Paint(); private final Path sPath = new Path(); private final Path bPath = new Path(); private final RectF bRectF = new RectF(); private float sAnim, bAnim; private RadialGradient shadowGradient; private final AccelerateInterpolator aInterpolator = new AccelerateInterpolator(2); /** * state switch on */ public static final int STATE_SWITCH_ON = 4; /** * state prepare to off */ public static final int STATE_SWITCH_ON2 = 3; /** * state prepare to on */ public static final int STATE_SWITCH_OFF2 = 2; /** * state prepare to off */ public static final int STATE_SWITCH_OFF = 1; /** * current state */ private int state = STATE_SWITCH_OFF; /** * last state */ private int lastState = state; private int mWidth, mHeight; private float sWidth, sHeight; private float sLeft, sTop, sRight, sBottom; private float sCenterX, sCenterY; private float sScale; private float bOffset; private float bRadius, bStrokeWidth; private float bWidth; private float bLeft, bTop, bRight, bBottom; private float bOnLeftX, bOn2LeftX, bOff2LeftX, bOffLeftX; private float shadowHeight; int xFirst=0; public SwitchView(Context context) { this(context, null); } public SwitchView(Context context, AttributeSet attrs) { super(context, attrs); setLayerType(LAYER_TYPE_SOFTWARE, null); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = (int) (widthSize * 0.65f); setMeasuredDimension(widthSize, heightSize); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; sLeft = sTop = 0; sRight = mWidth; sBottom = mHeight * 0.91f; sWidth = sRight - sLeft; sHeight = sBottom - sTop; sCenterX = (sRight + sLeft) / 2; sCenterY = (sBottom + sTop) / 2; shadowHeight = mHeight - sBottom; bLeft = bTop = 0; bRight = bBottom = sBottom; bWidth = bRight - bLeft; final float halfHeightOfS = (sBottom - sTop) / 2; bRadius = halfHeightOfS * 0.95f; bOffset = bRadius * 0.2f; bStrokeWidth = (halfHeightOfS - bRadius) * 2; bOnLeftX = sWidth - bWidth; bOn2LeftX = bOnLeftX - bOffset; bOffLeftX = 0; bOff2LeftX = 0; sScale = 1 - bStrokeWidth / sHeight; RectF sRectF = new RectF(sLeft, sTop, sBottom, sBottom); sPath.arcTo(sRectF, 90, 180); sRectF.left = sRight - sBottom; sRectF.right = sRight; sPath.arcTo(sRectF, 270, 180); sPath.close(); bRectF.left = bLeft; bRectF.right = bRight; bRectF.top = bTop + bStrokeWidth / 2; bRectF.bottom = bBottom - bStrokeWidth / 2; shadowGradient = new RadialGradient(bWidth / 2, bWidth / 2, bWidth / 2, 0xff000000, 0x00000000, Shader.TileMode.CLAMP); } private void calcBPath(float percent) { bPath.reset(); bRectF.left = bLeft + bStrokeWidth / 2; bRectF.right = bRight - bStrokeWidth / 2; bPath.arcTo(bRectF, 90, 180); bRectF.left = bLeft + percent * bOffset + bStrokeWidth / 2; bRectF.right = bRight + percent * bOffset - bStrokeWidth / 2; bPath.arcTo(bRectF, 270, 180); bPath.close(); } private float calcBTranslate(float percent) { float result = 0; int wich = state - lastState; switch (wich) { case 1: // off -> off2 if (state == STATE_SWITCH_OFF2) { result = bOff2LeftX - (bOff2LeftX - bOffLeftX) * percent; } // on2 -> on else if (state == STATE_SWITCH_ON) { result = bOnLeftX - (bOnLeftX - bOn2LeftX) * percent; } break; case 2: // off2 -> on if (state == STATE_SWITCH_ON) { result = bOnLeftX - (bOnLeftX - bOff2LeftX) * percent; } // off -> on2 else if (state == STATE_SWITCH_ON) { result = bOn2LeftX - (bOn2LeftX - bOffLeftX) * percent; } break; case 3: // off -> on result = bOnLeftX - (bOnLeftX - bOffLeftX) * percent; break; case -1: // on -> on2 if (state == STATE_SWITCH_ON2) { result = bOn2LeftX + (bOnLeftX - bOn2LeftX) * percent; } // off2 -> off else if (state == STATE_SWITCH_OFF) { result = bOffLeftX + (bOff2LeftX - bOffLeftX) * percent; } break; case -2: // on2 -> off if (state == STATE_SWITCH_OFF) { result = bOffLeftX + (bOn2LeftX - bOffLeftX) * percent; } // on -> off2 else if (state == STATE_SWITCH_OFF2) { result = bOff2LeftX + (bOnLeftX - bOff2LeftX) * percent; } break; case -3: // on -> off result = bOffLeftX + (bOnLeftX - bOffLeftX) * percent; break; } return result - bOffLeftX; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paint.setAntiAlias(true); final boolean isOn = (state == STATE_SWITCH_ON || state == STATE_SWITCH_ON2); // draw background paint.setStyle(Style.FILL); paint.setColor(isOn ? 0xff4bd763 : 0xffe3e3e3); canvas.drawPath(sPath, paint); sAnim = sAnim - 0.1f > 0 ? sAnim - 0.1f : 0; bAnim = bAnim - 0.1f > 0 ? bAnim - 0.1f : 0; final float dsAnim = aInterpolator.getInterpolation(sAnim); final float dbAnim = aInterpolator.getInterpolation(bAnim); // draw background animation final float scale = sScale * (isOn ? dsAnim : 1 - dsAnim); final float scaleOffset = (bOnLeftX + bRadius - sCenterX) * (isOn ? 1 - dsAnim : dsAnim); canvas.save(); canvas.scale(scale, scale, sCenterX + scaleOffset, sCenterY); paint.setColor(0xffffffff); canvas.drawPath(sPath, paint); canvas.restore(); // draw center bar canvas.save(); canvas.translate(calcBTranslate(dbAnim), shadowHeight); final boolean isState2 = (state == STATE_SWITCH_ON2 || state == STATE_SWITCH_OFF2); calcBPath(isState2 ? 1 - dbAnim : dbAnim); // draw shadow paint.setStyle(Style.FILL); paint.setColor(0xff333333); paint.setShader(shadowGradient); canvas.drawPath(bPath, paint); paint.setShader(null); canvas.translate(0, -shadowHeight); canvas.scale(0.98f, 0.98f, bWidth / 2, bWidth / 2); paint.setStyle(Style.FILL); paint.setColor(0xffffffff); canvas.drawPath(bPath, paint); paint.setStyle(Style.STROKE); paint.setStrokeWidth(bStrokeWidth * 0.5f); paint.setColor(isOn ? 0xff4ada60 : 0xffbfbfbf); canvas.drawPath(bPath, paint); canvas.restore(); paint.reset(); if (sAnim > 0 || bAnim > 0) invalidate(); } @Override public boolean onTouchEvent(MotionEvent event) { if ((state == STATE_SWITCH_ON || state == STATE_SWITCH_OFF) && (sAnim * bAnim == 0)) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: xFirst =(int)event.getRawX(); return true; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: int xSecond=(int)event.getRawX(); int distance=xSecond-xFirst; lastState = state; if(distance==0) { if (state == STATE_SWITCH_OFF) { refreshState(STATE_SWITCH_OFF2); actionSwitch(); } else if (state == STATE_SWITCH_ON) { refreshState(STATE_SWITCH_ON2); actionSwitch(); } } //???? if(distance>0){ if (state == STATE_SWITCH_OFF) { refreshState(STATE_SWITCH_OFF2); actionSwitch(); } } //???? if(distance<0){ if (state == STATE_SWITCH_ON) { refreshState(STATE_SWITCH_ON2); actionSwitch(); } } break; } } return super.onTouchEvent(event); } private void actionSwitch(){ if (listener != null) { if (state == STATE_SWITCH_OFF2 ) { listener.toggleToOn(); } else if (state == STATE_SWITCH_ON2) { listener.toggleToOff(); } } } private void refreshState(int newState) { lastState = state; state = newState; postInvalidate(); bAnim = 1; invalidate(); } /** * * @return the state of switch view */ public int getState() { return state; } /** * if set true , the state change to on; * if set false, the state change to off * @param isOn */ public void setState(boolean isOn) { final int wich = isOn ? STATE_SWITCH_ON : STATE_SWITCH_OFF; if(state!=wich) { refreshState(wich); } } /** * if set true , the state change to on; * if set false, the state change to off *
change state with animation * @param letItOn */ public void toggleSwitch(boolean letItOn) { final int wich = letItOn ? STATE_SWITCH_ON : STATE_SWITCH_OFF; postDelayed(new Runnable() { @Override public void run() { toggleSwitch(wich); } }, 100); } private synchronized void toggleSwitch(int wich) { if (wich == STATE_SWITCH_ON || wich == STATE_SWITCH_OFF) { if ((wich == STATE_SWITCH_ON && (lastState == STATE_SWITCH_OFF || lastState == STATE_SWITCH_OFF2)) || (wich == STATE_SWITCH_OFF && (lastState == STATE_SWITCH_ON || lastState == STATE_SWITCH_ON2))) { sAnim = 1; } bAnim = 1; refreshState(wich); } } public interface OnStateChangedListener { void toggleToOn(); void toggleToOff(); } private OnStateChangedListener listener = new OnStateChangedListener() { @Override public void toggleToOn() { toggleSwitch(STATE_SWITCH_ON); } @Override public void toggleToOff() { toggleSwitch(STATE_SWITCH_OFF); } }; public void setOnStateChangedListener(OnStateChangedListener listener) { if (listener == null) throw new IllegalArgumentException("empty listener"); this.listener = listener; } }