/* * Copyright (C) 2012 www.amsoft.cn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ab.view.sliding; import android.content.Context; 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.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.CheckBox; import com.ab.util.AbLogUtil; // TODO: Auto-generated Javadoc /** * © 2012 amsoft.cn * 名称:AbSlidingButton.java * 描述:滑动按钮 * * @author 还如一梦中 * @version v1.0 * @date:2013-11-14 上午11:31:21 */ public class AbSlidingButton extends CheckBox { /** The context. */ private Context context; //图片资源 /** The btn frame. */ private Bitmap btnFrame; /** The btn mask. */ private Bitmap btnMask; /** The btn bottom. */ private Bitmap btnBottom; /** The m btn normal. */ private Bitmap mBtnNormal; /** The m btn pressed. */ private Bitmap mBtnPressed; /** The on checked change listener. */ private OnCheckedChangeListener onCheckedChangeListener; //记录开关当前的状态 /** The is checked. */ private boolean isChecked; //背景的宽高 /** The m mask height. */ private float mMaskHeight; /** The m mask width. */ private float mMaskWidth; /** The m btn off pos. */ private float mBtnOffPos; /** The m btn on pos. */ private float mBtnOnPos; /** The m btn pos. */ private float mBtnPos; /** The m last btn pos. */ private float mLastBtnPos; /** The m real pos. */ private float mRealPos; /** The m btn width. */ private float mBtnWidth; //Y方向的延伸 /** The m extend offset y. */ private float mExtendOffsetY; /** The m alpha. */ private int mAlpha; /** The m paint. */ private Paint mPaint; /** The m xfermode. */ private PorterDuffXfermode mXfermode; /** The m save layer rect f. */ private RectF mSaveLayerRectF; /** The m cur btn pic. */ private Bitmap mCurBtnPic; /** The m first down x. */ private float mFirstDownX; /** The m move event. */ private boolean mMoveEvent; /** The m animating. */ private boolean mAnimating; /** The m animation position. */ private float mAnimationPosition; /** The m animated velocity. */ private float mAnimatedVelocity; /** The handler. */ private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: mRealPos = getRealPos((Float)msg.obj); mLastBtnPos = (Float)msg.obj; invalidate(); break; default: break; } } }; /** * Instantiates a new ab sliding button. * * @param context the context */ public AbSlidingButton(Context context) { super(context); init(context); } /** * Instantiates a new ab sliding button. * * @param context the context * @param attrs the attrs */ public AbSlidingButton(Context context, AttributeSet attrs) { super(context, attrs); init(context); } /** * Inits the. * * @param context the context */ private void init(Context context) { this.context = context; this.mAlpha = 255; this.isChecked = false; this.mPaint = new Paint(); this.mPaint.setColor(Color.WHITE); float density = getResources().getDisplayMetrics().density; this.mAnimatedVelocity = (int)(0.5F + 350.0F * density); } /* (non-Javadoc) * @see android.widget.CompoundButton#onDraw(android.graphics.Canvas) */ protected void onDraw(Canvas canvas){ //设置一个透明度为255的图层 canvas.saveLayerAlpha(this.mSaveLayerRectF, this.mAlpha,Canvas.ALL_SAVE_FLAG); canvas.drawBitmap(this.btnMask, 0F, this.mExtendOffsetY, this.mPaint); //混合绘制,取两层绘制交集。显示上层。btnBottom的长度超出btnMask的部分不绘制 this.mPaint.setXfermode(this.mXfermode); canvas.drawBitmap(this.btnBottom, this.mRealPos, this.mExtendOffsetY, this.mPaint); this.mPaint.setXfermode(null); canvas.drawBitmap(this.btnFrame, 0F, this.mExtendOffsetY, this.mPaint); canvas.drawBitmap(this.mCurBtnPic, this.mRealPos, 0.40000000596046448F + this.mExtendOffsetY, this.mPaint); canvas.restore(); } /** * 描述:测量View的宽高. * * @param paramInt1 the param int1 * @param paramInt2 the param int2 * @see android.view.View#onMeasure(int, int) */ protected void onMeasure(int paramInt1, int paramInt2){ setMeasuredDimension((int)this.mMaskWidth, (int)(this.mMaskHeight + 2F * this.mExtendOffsetY)); } /** * 描述:设置图片资源. * * @version v1.0 * @param btnBottomResource the btn bottom resource * @param btnFrameResource the btn frame resource * @param btnMaskResource the btn mask resource * @param btnNormalResource the btn normal resource * @param btnPressedResource the btn pressed resource * @date:2013-11-29 下午3:26:42 */ public void setImageResource(int btnBottomResource,int btnFrameResource,int btnMaskResource, int btnNormalResource,int btnPressedResource) { btnBottom = BitmapFactory.decodeResource(context.getResources(),btnBottomResource); btnFrame = BitmapFactory.decodeResource(context.getResources(),btnFrameResource); btnMask = BitmapFactory.decodeResource(context.getResources(),btnMaskResource); mBtnNormal = BitmapFactory.decodeResource(context.getResources(),btnNormalResource); mBtnPressed = BitmapFactory.decodeResource(context.getResources(),btnPressedResource); this.mMaskWidth = this.btnMask.getWidth(); this.mMaskHeight = this.btnMask.getHeight(); float density = getResources().getDisplayMetrics().density; this.mExtendOffsetY = (int)(0.5F + density * 0F); this.mSaveLayerRectF = new RectF(-20.0F, this.mExtendOffsetY, 20 + this.mMaskWidth, this.mMaskHeight + this.mExtendOffsetY); this.mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); this.mCurBtnPic = this.mBtnNormal; this.mBtnWidth = this.mBtnPressed.getWidth(); this.mBtnOnPos = (this.mBtnWidth / 2F); this.mBtnOffPos = (this.mMaskWidth - this.mBtnWidth / 2F); if (isChecked){ this.mBtnPos = mBtnOnPos; }else{ this.mBtnPos = mBtnOffPos; } this.mRealPos = getRealPos(this.mBtnPos); } /** * Gets the real pos. * * @param paramFloat the param float * @return the real pos */ private float getRealPos(float paramFloat){ return (paramFloat - this.mBtnWidth / 2F); } /** * 描述:获得当前. * * @version v1.0 * @return true, if is checked * @see android.widget.CompoundButton#isChecked() * @author: amsoft.cn * @date:2013-11-29 下午3:54:47 */ public boolean isChecked() { return isChecked; } /** * 描述:设置选中. * * @version v1.0 * @param checked the new checked * @see android.widget.CompoundButton#setChecked(boolean) * @author: amsoft.cn * @date:2013-11-29 下午3:54:33 */ public void setChecked(boolean checked) { setChecked(checked,false); } /** * 描述:设置选中. * * @version v1.0 * @param checked the checked * @param anim the anim * @see android.widget.CompoundButton#setChecked(boolean) * @author: amsoft.cn * @date:2013-11-29 下午3:54:33 */ public void setChecked(boolean checked,boolean anim) { if(checked){ this.mBtnPos = this.mBtnOnPos; }else{ this.mBtnPos = this.mBtnOffPos; } if(anim){ startAnimation(); }else{ moveViewToTarget(); } } /** * 描述:设置监听器. * * @version v1.0 * @param listener the new on checked change listener * @see android.widget.CompoundButton#setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener) * @author: amsoft.cn * @date:2013-11-29 下午3:54:20 */ public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { onCheckedChangeListener = listener; } /** * 描述:滑动事件. * * @version v1.0 * @param event the event * @return true, if successful * @see android.widget.TextView#onTouchEvent(android.view.MotionEvent) * @author: amsoft.cn * @date:2013-12-5 上午11:22:12 */ public boolean onTouchEvent(MotionEvent event){ if(this.mAnimating){ return true; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mMoveEvent = false; //当前点击位置 this.mFirstDownX = event.getX(); //点击效果 this.mCurBtnPic = this.mBtnPressed; //当前滑块再的位置 if (this.isChecked){ this.mBtnPos = this.mBtnOnPos; }else{ this.mBtnPos = this.mBtnOffPos; } mLastBtnPos = mBtnPos; AbLogUtil.d(AbSlidingButton.class, "原来的X位置:"+this.mBtnPos); break; case MotionEvent.ACTION_MOVE: AbLogUtil.d(AbSlidingButton.class, "----移动----"); //当前点击位置 float x = event.getX(); //差 float offsetX = x - this.mFirstDownX; AbLogUtil.d(AbSlidingButton.class, "X需要移动:"+offsetX); //转换为点击事件 if(Math.abs(offsetX) < 5){ break; }else{ mMoveEvent = true; } this.mFirstDownX = event.getX(); //移动后的应该在的位置 this.mBtnPos = this.mBtnPos + offsetX; AbLogUtil.d(AbSlidingButton.class, "现在的X位置:"+this.mBtnPos); //超出控件的设置 if (this.mBtnPos < this.mBtnOffPos){ this.mBtnPos = this.mBtnOffPos; } if (this.mBtnPos > this.mBtnOnPos){ this.mBtnPos = this.mBtnOnPos; } moveViewToTarget(); break; default: if(mMoveEvent){ AbLogUtil.d(AbSlidingButton.class, "----弹起----"); //弹起 this.mCurBtnPic = this.mBtnNormal; //本次移动最后结果 if (this.mBtnPos < (this.mBtnOnPos-this.mBtnOffPos) / 2F + this.mBtnOffPos){ this.mBtnPos = this.mBtnOffPos; }else{ this.mBtnPos = this.mBtnOnPos; } startAnimation(); offsetX = 0; }else{ //点击事件 setChecked(!isChecked,true); } break; } return true; } /** * 描述:滑块移动. * * @version v1.0 * @param pos the pos * @date:2013-12-5 上午11:46:56 */ private void moveView(float pos){ moveView(pos,false); } /** * 描述:滑块移动. * * @version v1.0 * @param pos the pos * @param delay the delay * @date:2013-12-5 上午11:46:56 */ private void moveView(final float pos,boolean delay){ if(handler!=null){ handler.obtainMessage(0, pos).sendToTarget(); } } /** * 描述:用位移加速度实现动画. * * @version v1.0 * @date:2013-12-9 上午9:47:59 */ private void startAnimation(){ //已经在目标位置 if(mLastBtnPos == this.mBtnPos){ return; } this.mAnimating = true; AbLogUtil.d(AbSlidingButton.class, "目标移动X到:"+this.mBtnPos+",当前在:"+mLastBtnPos); float mVelocity = this.mAnimatedVelocity; if(mLastBtnPos > this.mBtnPos){ mVelocity = -this.mAnimatedVelocity; } this.mAnimationPosition = mLastBtnPos; int i = 0; while(true){ this.mAnimationPosition = (this.mAnimationPosition + 16.0F * mVelocity / 1000.0F); AbLogUtil.d(AbSlidingButton.class, i+"次移动X到:"+this.mAnimationPosition); if(this.mAnimationPosition >= this.mBtnOnPos){ this.mAnimationPosition = this.mBtnOnPos; moveView(this.mAnimationPosition,true); if(!isChecked){ isChecked = true; if(onCheckedChangeListener!=null){ onCheckedChangeListener.onCheckedChanged(this, isChecked); } } break; }else if(this.mAnimationPosition <= this.mBtnOffPos){ this.mAnimationPosition = this.mBtnOffPos; moveView(this.mAnimationPosition,true); if(isChecked){ isChecked = false; if(onCheckedChangeListener!=null){ onCheckedChangeListener.onCheckedChanged(this, isChecked); } } break; }else{ moveView(this.mAnimationPosition,true); } i++; } this.mAnimating = false; } /** * 描述:直接移动到位置. * * @version v1.0 * @date:2013-12-9 上午9:20:38 */ private void moveViewToTarget(){ moveView(this.mBtnPos); if (this.mBtnPos == this.mBtnOnPos){ if(!isChecked){ isChecked = true; if(onCheckedChangeListener!=null){ onCheckedChangeListener.onCheckedChanged(this, isChecked); } } return; }else if(this.mBtnPos == this.mBtnOffPos){ if(isChecked){ isChecked = false; if(onCheckedChangeListener!=null){ onCheckedChangeListener.onCheckedChanged(this, isChecked); } } return; } } /* (non-Javadoc) * @see android.widget.CompoundButton#performClick() */ @Override public boolean performClick() { setChecked(!isChecked); return true; } /* (non-Javadoc) * @see android.widget.CompoundButton#toggle() */ @Override public void toggle() { this.setChecked(!isChecked); } }