import com.example.compoundbuttonview.R;import com.example.compoundbuttonview.anim.FrameAnimationController;import android.content.Context;import android.content.res.Resources;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.RectF;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.ViewConfiguration;import android.view.ViewParent;import android.widget.CheckBox;import android.widget.CompoundButton.OnCheckedChangeListener;public class CheckSwitchButton extends CheckBox {/** 画笔 */private Paint mPaint;private ViewParent mParent;private Bitmap mBottom;private Bitmap mCurBtnPic;private Bitmap mBtnPressed;private Bitmap mBtnNormal;/** 整体框架 */private Bitmap mFrame;/** 阴影层,不能点击的时候显示 */private Bitmap mMask;/** 保存布局的矩阵 */private RectF mSaveLayerRectF;/** 布局之间叠层,好比背景和背景上的图片效果 */private PorterDuffXfermode mXfermode;/** 首次按下的Y */private float mFirstDownY;/** 首次按下的X */private float mFirstDownX;/** 图片的绘制位置 */private float mRealPos;/** 按钮的位置 */private float mBtnPos;/** 开关打开的位置 */private float mBtnOnPos;/** 开关关闭的位置 */private float mBtnOffPos;/** 阴影的宽度 */private float mMaskWidth;/** 阴影的高度 */private float mMaskHeight;/** 开关圆形按钮的宽度 */private float mBtnWidth;/** 开关初始坐标 */private float mBtnInitPos;private int mClickTimeout;private int mTouchSlop;/** 最大透明度,就是不透明 */private final int MAX_ALPHA = 255;/** 当前透明度,这里主要用于如果控件的enable属性为false时候设置半透明 ,即不可以点击 */private int mAlpha = MAX_ALPHA;private boolean mChecked = false;private boolean mBroadcasting;private boolean mTurningOn;private PerformClick mPerformClick;/** 开关状态切换监听接口 */private OnCheckedChangeListener mOnCheckedChangeListener;private OnCheckedChangeListener mOnCheckedChangeWidgetListener;/** 判断是否在进行动画  */private boolean mAnimating;private final float VELOCITY = 350;/** 滑动速度 */private float mVelocity;private final float EXTENDED_OFFSET_Y = 15;/** Y轴方向扩大的区域,增大点击区域 */private float mExtendOffsetY;private float mAnimationPosition;private float mAnimatedVelocity;public CheckSwitchButton(Context context, AttributeSet attrs) {this(context, attrs, android.R.attr.checkboxStyle);}public CheckSwitchButton(Context context) {this(context, null);}public CheckSwitchButton(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initView(context);}private void initView(Context context) {mPaint = new Paint();mPaint.setColor(Color.WHITE);Resources resources = context.getResources();// get viewConfigurationmClickTimeout = ViewConfiguration.getPressedStateDuration()+ ViewConfiguration.getTapTimeout();mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();// get BitmapmBottom = BitmapFactory.decodeResource(resources, R.drawable.checkswitch_bottom);mBtnPressed = BitmapFactory.decodeResource(resources,R.drawable.checkswitch_btn_pressed);mBtnNormal = BitmapFactory.decodeResource(resources,R.drawable.checkswitch_btn_unpressed);mFrame = BitmapFactory.decodeResource(resources, R.drawable.checkswitch_frame);mMask = BitmapFactory.decodeResource(resources, R.drawable.checkswitch_mask);mCurBtnPic = mBtnNormal;mBtnWidth = mBtnPressed.getWidth();mMaskWidth = mMask.getWidth();mMaskHeight = mMask.getHeight();mBtnOffPos = mBtnWidth / 2;mBtnOnPos = mMaskWidth - mBtnWidth / 2;// 判断起始位置,如果设定了mChecked为true,起始位置为 mBtnOnPosmBtnPos = mChecked ? mBtnOnPos : mBtnOffPos;mRealPos = getRealPos(mBtnPos);// density 密度final float density = getResources().getDisplayMetrics().density;// 方法是获取资源密度(Density)mVelocity = (int) (VELOCITY * density + 0.5f);mExtendOffsetY = (int) (EXTENDED_OFFSET_Y * density + 0.5f);// 创建一个新的矩形与指定的坐标。mSaveLayerRectF = new RectF(0, mExtendOffsetY, mMask.getWidth(),mMask.getHeight() + mExtendOffsetY);mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);// PorterDuff.Mode.SRC_IN// :这个属性代表// 取两层绘制交集。显示上层。}@Overridepublic void setEnabled(boolean enabled) {mAlpha = enabled ? MAX_ALPHA : MAX_ALPHA / 2;super.setEnabled(enabled);}public boolean isChecked() {return mChecked;}/** 自动判断切换至相反的属性 : true -->false ;false -->true */public void toggle() {setChecked(!mChecked);}/** * 内部调用此方法设置checked状态,此方法会延迟执行各种回调函数,保证动画的流畅度 *  * @param checked */private void setCheckedDelayed(final boolean checked) {this.postDelayed(new Runnable() {@Overridepublic void run() {setChecked(checked);}}, 10);}/** * <p> * Changes the checked state of this button. * </p> *  * @param checked *            true to check the button, false to uncheck it */public void setChecked(boolean checked) {if (mChecked != checked) {mChecked = checked;mBtnPos = checked ? mBtnOnPos : mBtnOffPos;mRealPos = getRealPos(mBtnPos);invalidate();// Avoid infinite recursions if setChecked() is called from a// listenerif (mBroadcasting) {return;}mBroadcasting = true;if (mOnCheckedChangeListener != null) {mOnCheckedChangeListener.onCheckedChanged(CheckSwitchButton.this,mChecked);}if (mOnCheckedChangeWidgetListener != null) {mOnCheckedChangeWidgetListener.onCheckedChanged(CheckSwitchButton.this, mChecked);}mBroadcasting = false;}}/** * Register a callback to be invoked when the checked state of this button * changes. *  * @param listener *            the callback to call on checked state change */public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {mOnCheckedChangeListener = listener;}/** * Register a callback to be invoked when the checked state of this button * changes. This callback is used for internal purpose only. *  * @param listener *            the callback to call on checked state change * @hide */void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {mOnCheckedChangeWidgetListener = listener;}@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();float x = event.getX();float y = event.getY();float deltaX = Math.abs(x - mFirstDownX);float deltaY = Math.abs(y - mFirstDownY);switch (action) {case MotionEvent.ACTION_DOWN:attemptClaimDrag();mFirstDownX = x;mFirstDownY = y;mCurBtnPic = mBtnPressed;mBtnInitPos = mChecked ? mBtnOnPos : mBtnOffPos;break;case MotionEvent.ACTION_MOVE:// 拖动着的时间float time = event.getEventTime() - event.getDownTime();// 当前按钮的位置mBtnPos = mBtnInitPos + event.getX() - mFirstDownX;if (mBtnPos >= mBtnOffPos) {mBtnPos = mBtnOffPos;}if (mBtnPos <= mBtnOnPos) {mBtnPos = mBtnOnPos;}mTurningOn = mBtnPos > (mBtnOffPos - mBtnOnPos) / 2 + mBtnOnPos;mRealPos = getRealPos(mBtnPos);break;case MotionEvent.ACTION_UP:mCurBtnPic = mBtnNormal;time = event.getEventTime() - event.getDownTime();if (deltaY < mTouchSlop && deltaX < mTouchSlop&& time < mClickTimeout) {if (mPerformClick == null) {mPerformClick = new PerformClick();}if (!post(mPerformClick)) {performClick();}} else {startAnimation(!mTurningOn);}break;}invalidate();return isEnabled();}private final class PerformClick implements Runnable {public void run() {performClick();}}@Overridepublic boolean performClick() {startAnimation(!mChecked);return true;}/** * 通知父类不要拦截touch事件 Tries to claim the user's drag motion, and requests * disallowing any ancestors from stealing events in the drag. */private void attemptClaimDrag() {mParent = getParent();if (mParent != null) {// 通知父类不要拦截touch事件mParent.requestDisallowInterceptTouchEvent(true);}}/** * 将btnPos转换成RealPos *  * @param btnPos * @return */private float getRealPos(float btnPos) {return btnPos - mBtnWidth / 2;}@Overrideprotected void onDraw(Canvas canvas) {canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha, 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);// 绘制蒙板canvas.drawBitmap(mMask, 0, mExtendOffsetY, mPaint);mPaint.setXfermode(mXfermode);// 绘制底部图片canvas.drawBitmap(mBottom, mRealPos, mExtendOffsetY, mPaint);mPaint.setXfermode(null);// 绘制边框canvas.drawBitmap(mFrame, 0, mExtendOffsetY, mPaint);// 绘制按钮canvas.drawBitmap(mCurBtnPic, mRealPos, mExtendOffsetY, mPaint);canvas.restore();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension((int) mMaskWidth,(int) (mMaskHeight + 2 * mExtendOffsetY));}private void startAnimation(boolean turnOn) {mAnimating = true;mAnimatedVelocity = turnOn ? -mVelocity : mVelocity;mAnimationPosition = mBtnPos;new SwitchAnimation().run();}private void stopAnimation() {mAnimating = false;}private final class SwitchAnimation implements Runnable {@Overridepublic void run() {if (!mAnimating) {return;}doAnimation();FrameAnimationController.requestAnimationFrame(this);}}private void doAnimation() {mAnimationPosition += mAnimatedVelocity * FrameAnimationController.ANIMATION_FRAME_DURATION / 1000;if (mAnimationPosition <= mBtnOnPos) {stopAnimation();mAnimationPosition = mBtnOnPos;setCheckedDelayed(true);} else if (mAnimationPosition >= mBtnOffPos) {stopAnimation();mAnimationPosition = mBtnOffPos;setCheckedDelayed(false);}moveView(mAnimationPosition);}private void moveView(float position) {mBtnPos = position;mRealPos = getRealPos(mBtnPos);invalidate();}}

更多相关文章

  1. StickyScrollView解说
  2. android简单一级购物车
  3. android 第三方分享
  4. 过期API_AlertDialog
  5. Chapter 1 Introducing Android(安卓)Studio
  6. Android(安卓)线性布局 LinearLayout
  7. 关于setOnCheckedChangeListener的使用
  8. Android——基于ConstraintLayout实现的可拖拽位置控件
  9. Android(安卓)stdio笔记

随机推荐

  1. Android(安卓)通过 Intent 传递类对象
  2. 【Android(安卓)Developers Training】 7
  3. Android(安卓)res文件夹下资源定义及使用
  4. JAVA的整型与字符串相互转换 android
  5. android 程序完全退出的有效方法
  6. Android中Xlistview的使用
  7. 自定义Android(安卓)Gradle插件的3种方式
  8. Android(安卓)文字居中方法
  9. Android中的RxBus替换掉EventBus
  10. Android(安卓)ActionBar Tabs学习笔记