package me.xiaopan.android.examples.widget; import android.content.Context; import android.graphics.*; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.animation.*; import android.widget.CompoundButton; import android.widget.Scroller; import me.xiaopan.android.examples.R; /** * Created by xiaopan on 2014/3/27 0027. */ public class SwitchButton extends CompoundButton { private Bitmap frameBitmap; private Bitmap sliderMaskBitmap;//滑块遮罩图片 private Bitmap statusMaskBitmap;//状态遮罩图片 private Drawable statusDrawable; private Drawable sliderDrawable; private Paint paint; private PorterDuffXfermode porterDuffXfermode;//遮罩类型 private float touchX; private int slideX; private int minSlideX; private int maxSlideX; private int moveDistance; private int minChancgeDistance = 30; private boolean isMoved; private SwitchScroller switchScroller; public SwitchButton(Context context) { super(context); init(); } public SwitchButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SwitchButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init(){ paint = new Paint(); porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN); switchScroller = new SwitchScroller(getContext(), new AccelerateDecelerateInterpolator()); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //计算宽度 int measureWidth; switch (MeasureSpec.getMode(widthMeasureSpec)) { case MeasureSpec.AT_MOST://如果widthSize是当前视图可使用的最大宽度 measureWidth = frameBitmap != null?frameBitmap.getWidth():0; break; case MeasureSpec.EXACTLY://如果widthSize是当前视图可使用的绝对宽度 measureWidth = MeasureSpec.getSize(widthMeasureSpec); break; case MeasureSpec.UNSPECIFIED://如果widthSize对当前视图宽度的计算没有任何参考意义 measureWidth = frameBitmap != null?frameBitmap.getWidth():0; break; default: measureWidth = frameBitmap != null?frameBitmap.getWidth():0; break; } //计算高度 int measureHeight; switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST://如果heightSize是当前视图可使用的最大宽度 measureHeight = frameBitmap != null?frameBitmap.getHeight():0; break; case MeasureSpec.EXACTLY://如果heightSize是当前视图可使用的绝对宽度 measureHeight = MeasureSpec.getSize(heightMeasureSpec); break; case MeasureSpec.UNSPECIFIED://如果heightSize对当前视图宽度的计算没有任何参考意义 measureHeight = frameBitmap != null?frameBitmap.getHeight():0; break; default: measureHeight = frameBitmap != null?frameBitmap.getHeight():0; break; } setMeasuredDimension(measureWidth, measureHeight); } @Override protected void onDraw(Canvas canvas) { //保存并创建一个新的透明层,如果不这样做的话,画出来的背景会是黑的 canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); //绘制状态层 if(statusDrawable != null && statusMaskBitmap != null){ canvas.save(); canvas.translate(slideX, 0); statusDrawable.draw(canvas); canvas.restore(); paint.setXfermode(porterDuffXfermode); canvas.drawBitmap(statusMaskBitmap, 0, 0, paint); paint.setXfermode(null); } //绘制框架层 if(frameBitmap != null){ canvas.drawBitmap(frameBitmap, 0, 0, paint); } //绘制滑块层 if(sliderDrawable != null && sliderMaskBitmap != null){ canvas.save(); canvas.translate(slideX, 0); sliderDrawable.draw(canvas); canvas.restore(); paint.setXfermode(porterDuffXfermode); canvas.drawBitmap(sliderMaskBitmap, 0, 0, paint); paint.setXfermode(null); } super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); switch(event.getAction()){ case MotionEvent.ACTION_DOWN : isMoved = false; moveDistance = 0; touchX = event.getX(); setPressed(true); break; case MotionEvent.ACTION_MOVE : isMoved = true; float newTouchX = event.getX(); int currentDistance = (int) (newTouchX - touchX); moveDistance += currentDistance; setSlideX(slideX += currentDistance); touchX = newTouchX; invalidate(); break; case MotionEvent.ACTION_UP : setPressed(false); if(Math.abs(moveDistance) > 10){//当滑动距离大于10才会被认为这是一次有效的滑动操作 if(Math.abs(moveDistance) > minChancgeDistance){//如果滑动距离大于等于最小切换距离就切换状态 setChecked(!isChecked()); }else{ switchScroller.scroll(isChecked());//本次滑动无效,回滚 post(switchScroller); } }else{ setChecked(!isChecked()); //单击切换状态 } break; case MotionEvent.ACTION_CANCEL : switchScroller.scroll(isChecked()); post(switchScroller); break; case MotionEvent.ACTION_OUTSIDE : switchScroller.scroll(isChecked()); post(switchScroller); break; } return true; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if(statusDrawable != null){ statusDrawable.setState(getDrawableState()); } if(sliderDrawable != null){ sliderDrawable.setState(getDrawableState()); } invalidate(); } @Override public void setChecked(boolean checked) { super.setChecked(checked); if(switchScroller != null){ switchScroller.scroll(checked); post(switchScroller); } } public void setDrawables(Bitmap frameBitmap, Drawable statusDrawable, Bitmap statusMaskBitmap, Drawable sliderDrawable, Bitmap sliderMaskBitmap){ if(frameBitmap == null || statusDrawable == null || statusMaskBitmap == null || sliderDrawable == null || sliderMaskBitmap == null){ throw new IllegalArgumentException("ALL NOT NULL"); } this.frameBitmap = frameBitmap; this.statusDrawable = statusDrawable; this.statusMaskBitmap = statusMaskBitmap; this.sliderDrawable = sliderDrawable; this.sliderMaskBitmap = sliderMaskBitmap; this.statusDrawable.setBounds(0, 0, this.statusDrawable.getIntrinsicWidth(), this.statusDrawable.getIntrinsicHeight()); this.statusDrawable.setCallback(this); this.sliderDrawable.setBounds(0, 0, this.sliderDrawable.getIntrinsicWidth(), this.sliderDrawable.getIntrinsicHeight()); this.sliderDrawable.setCallback(this); this.minSlideX = (-1 * (statusDrawable.getIntrinsicWidth() - frameBitmap.getWidth())); requestLayout(); } public void setDrawableResIds(int frameBitmapResId, int statusDrawableResId, int statusMaskBitmapResId, int sliderDrawableResId, int sliderMaskBitmapResId){ setDrawables( BitmapFactory.decodeResource(getResources(), R.drawable.button_sliding_frame), getResources().getDrawable(R.drawable.selector_switch_status), BitmapFactory.decodeResource(getResources(), R.drawable.button_sliding_state_mask), getResources().getDrawable(R.drawable.selector_switch_slider), BitmapFactory.decodeResource(getResources(), R.drawable.button_sliding_slider_mask) ); } private void setSlideX(int slideX) { this.slideX = slideX; if(this.slideX < minSlideX){ this.slideX = minSlideX; } if(this.slideX > maxSlideX){ this.slideX = maxSlideX; } } private class SwitchScroller implements Runnable{ private Scroller scroller; public SwitchScroller(Context context, android.view.animation.Interpolator interpolator) { this.scroller = new Scroller(context, interpolator); } public void scroll(boolean checked){ scroller.startScroll(slideX, 0, (checked?minSlideX:maxSlideX) - slideX, 0, 300); } @Override public void run() { if(scroller.computeScrollOffset()){ setSlideX(scroller.getCurrX()); invalidate(); post(this); } } } }