package me.fichardu.jellotoggle; import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.Scroller; /** * Created by xf on 15/4/14. */ public class JelloToggle extends FrameLayout { private static final int DEFAULT_DURATION = 500; private static final int UNCHECKED_JELLO_COLOR = 0xffadadad; private static final int CHECKED_JELLO_COLOR = 0xffff0000; private Rect mJelloRect; private Paint mJelloPaint; private Scroller mScroller; private Path mJelloPath; private TimeInterpolator mInterpolator; private OnCheckedChangeListener mListener; private Drawable mCheckedDrawable; private Drawable mOnCheckDrawable; private Drawable mUnCheckedDrawable; private Drawable mDrawable; private boolean mChecked = false; private int mTouchStartX; private int mScrollOffset; private int mJelloSize; private int mDragLimit; private int mJelloMax; private int mJelloOffset; private long mStartTime; private long mDuration; private int mCheckedColor = CHECKED_JELLO_COLOR; public JelloToggle(Context context) { super(context); init(); } public JelloToggle(Context context, AttributeSet attrs) { super(context, attrs); init(); } public JelloToggle(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mJelloPaint = new Paint(); mJelloPaint.setAntiAlias(true); mCheckedDrawable = getResources().getDrawable(R.drawable.checked); mOnCheckDrawable = getResources().getDrawable(R.drawable.check_on); mUnCheckedDrawable = getResources().getDrawable(R.drawable.uncheck); setJelloState(); mJelloRect = new Rect(); mScroller = new Scroller(getContext()); mJelloPath = new Path(); mInterpolator = new EaseOutElasticInterpolator(); mDuration = DEFAULT_DURATION; } private void calPath() { mJelloPath.rewind(); mJelloPath.moveTo(mJelloRect.right, 0); mJelloPath.lineTo(mJelloRect.left, 0); mJelloPath.cubicTo(mJelloRect.left, mJelloSize / 2, mJelloRect.left + mJelloOffset - mJelloSize / 3, mJelloSize * 3 / 4, mJelloRect.left, mJelloSize); mJelloPath.lineTo(mJelloRect.right, mJelloRect.bottom); mJelloPath.close(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mDragLimit = getMeasuredWidth() / 4; mJelloSize = getMeasuredHeight(); mJelloRect.set(getMeasuredWidth() - mJelloSize, 0, getMeasuredWidth() + mDragLimit, mJelloSize); mCheckedDrawable.setBounds(mJelloRect.left, mJelloRect.top, mJelloRect.left + mJelloSize, mJelloSize); mOnCheckDrawable.setBounds(mJelloRect.left, mJelloRect.top, mJelloRect.left + mJelloSize, mJelloSize); mUnCheckedDrawable.setBounds(mJelloRect.left, mJelloRect.top, mJelloRect.left + mJelloSize, mJelloSize); calPath(); } @Override protected void dispatchDraw(Canvas canvas) { canvas.save(); canvas.translate(mScrollOffset, 0); super.dispatchDraw(canvas); canvas.restore(); canvas.save(); canvas.translate(mScrollOffset / 2, 0); canvas.drawPath(mJelloPath, mJelloPaint); mDrawable.draw(canvas); canvas.restore(); } @Override public boolean onTouchEvent(MotionEvent event) { boolean ret = true; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.forceFinished(true); } mTouchStartX = (int) event.getX(); mDrawable = mOnCheckDrawable; break; case MotionEvent.ACTION_MOVE: int dragLen = Math.min(0, (int) event.getX() - mTouchStartX); mScrollOffset = Math.max(-mDragLimit, dragLen); mJelloOffset = dragLen; calPath(); postInvalidate(); break; case MotionEvent.ACTION_UP: if (mScrollOffset < 0) { mScroller.startScroll(mScrollOffset, 0, -mScrollOffset, 0); mJelloMax = mJelloOffset; if (mJelloOffset <= -mDragLimit) { mChecked = !mChecked; if (mListener != null) { mListener.onCheckedChange(mChecked); } } setJelloState(); postInvalidate(); startJello(); } break; } return ret; } private void setJelloState() { if (mChecked) { mJelloPaint.setColor(mCheckedColor); mDrawable = mCheckedDrawable; } else { mJelloPaint.setColor(UNCHECKED_JELLO_COLOR); mDrawable = mUnCheckedDrawable; } } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { mScrollOffset = mScroller.getCurrX(); ViewCompat.postInvalidateOnAnimation(this); } } public void setJelloDuration(long duration) { if (duration <= 0) { duration = DEFAULT_DURATION; } mDuration = duration; } public void setCheckedJelloColor(int color) { mCheckedColor = color; setJelloState(); postInvalidate(); } public void setCheckedDrawable(Drawable drawable) { mCheckedDrawable = drawable; } public void setOnCheckDrawable(Drawable drawable) { mOnCheckDrawable = drawable; } public void setUnCheckedDrawable(Drawable drawable) { mUnCheckedDrawable = drawable; } private void startJello() { mStartTime = AnimationUtils.currentAnimationTimeMillis(); post(mJelloRunnable); } private Runnable mJelloRunnable = new Runnable() { @Override public void run() { long playTime = AnimationUtils.currentAnimationTimeMillis() - mStartTime; if (playTime < mDuration) { float fraction = playTime / (float) mDuration; mJelloOffset = (int) (mJelloMax * (1 - mInterpolator.getInterpolation (fraction))); calPath(); ViewCompat.postInvalidateOnAnimation(JelloToggle.this); post(this); } else { mJelloOffset = 0; calPath(); ViewCompat.postInvalidateOnAnimation(JelloToggle.this); } } }; public interface OnCheckedChangeListener { void onCheckedChange(boolean checked); } public void setCheckedChangeListener(OnCheckedChangeListener listener) { mListener = listener; } }