参考文档:java
http://blog.csdn.net/singwhatiwanna/article/details/9254309android
Android开发文档API的View
canvas
本文中自定义开关MySwitch是我在阅读了上述文档,根据本身的理解写的,所引用的图片资源来自http://home.ustc.edu.cn/~voa/res/HelloJni.apk。ide
代码已在Android 2.2的模拟器和Android 4.2.1的手机上测试,都可运行。post
手机测试效果以下:
测试
a 默认效果
动画
b 向右滑,滑过中间位置动效果
this
c 松开手指,自动滑到右部效果
spa
d 向左滑,滑过中间位置动效果.net
松开手指,又回到a所示状态。
MySwitch代码:
package cn.mswitch; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; /** * 自定义开关按钮类,打开状态值为true,关闭为false * @author liuwh * @since 2014-05-2 */ public class MySwitch extends View { private static final String TAG = "MySwitch"; /** * 关闭状态 */ private static final int STATE_OFF = 0; /** * 打开状态 */ private static final int STATE_ON = 1; /** * 正在滑动状态 */ private static final int STATE_SCROLL = 2; private static Bitmap bitmapOFF;//关闭状态背景图片 private static Bitmap bitmapON;//打开状态背景图片 private static Bitmap bitmapThumb;//圆点图片(为正方形) private static int bgWidth;//背景宽度 private static int bgHeigth;//背景高度 private static int thumbWidth;//圆点宽度 /** * MySwitch状态标志,值为true或false,默认为false,即关闭 */ private boolean mState = false; /** * 滑动事件标志,true为滑动事件,false则为点击事件 */ private boolean mHasScroll = false; /** * MySwitch当前状态标志,值为0,1,2, * 0对应状态STATE_OFF * 1对应状态STATE_ON * 2对应状态STATE_SCROLL */ private int currState = 0; public MySwitch(Context context) { super(context); //DisplayMetrics dm = new DisplayMetrics(); //float density = context.getResources().getDisplayMetrics().density; //Log.i(TAG, "density= " + density);//屏幕密度 } public MySwitch(Context context, AttributeSet ats) { super(context, ats); init(); } public MySwitch(Context context, AttributeSet ats, int dfStyle) { super(context, ats, dfStyle); init(); } /** * 初始化图片对象,宽高数据和当前状态 */ private void init() { Resources res = getResources(); bitmapThumb = BitmapFactory.decodeResource(res, R.drawable.switch_thumb); bitmapON = BitmapFactory.decodeResource(res, R.drawable.bg_switch_on); bitmapOFF = BitmapFactory.decodeResource(res, R.drawable.bg_switch_off); bgWidth = bitmapON.getWidth(); bgHeigth = bitmapON.getHeight(); thumbWidth = bitmapThumb.getWidth(); currState = mState ? STATE_ON : STATE_OFF; Log.i(TAG, "bw: " + bgWidth + ", bh: " + bgHeigth + ", tw: " + thumbWidth); } @Override public void setLayoutParams(LayoutParams params) { //设置MySwitch的View区域宽高 params.width = bgWidth; params.height = bgHeigth; super.setLayoutParams(params); } /** * 获取MySwitch对象的状态 * @return boolean值 */ public boolean getMState(){ return this.mState; } /** * 设置MySwitch对象的状态 * @param isOn boolean值 */ public void setMState(boolean mState){ if(this.mState != mState){ //更改当前状态currState的值 currState = mState ? STATE_ON : STATE_OFF; //通知从新画图 MySwitch.this.postInvalidate(); } this.mState = mState; } //状态改变监听接口对象 private OnMySwicthStateChangedListener msl; /** * 设置状态改变监听 * @param mslOnMySwicthStateChangedListener对象 */ public void setOnMySwicthStateChangedListener(OnMySwicthStateChangedListener msl){ this.msl = msl; } private int startX; private int currX = 0; @Override public boolean onTouchEvent(MotionEvent event) { int pointers = event.getPointerCount(); //若是不是一个手指,则不作处理 if(pointers > 1) return true; int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN://手指按下 startX = (int) event.getX(); //Log.i(TAG, "start: " + startX); break; case MotionEvent.ACTION_MOVE://手指滑动 //设置滑动标志为true,并将当前状态置为滑动 mHasScroll = true; currState = STATE_SCROLL; currX = (int)event.getX(); currX = currX>bgWidth ? bgWidth : currX; //通知重画MySwitch的View区域 this.invalidate(); //Log.i(TAG, "curr x: " + currX ); break; case MotionEvent.ACTION_UP://手指松开 int endX = (int)event.getX(); //Log.i(TAG, "endX: " + endX); int midX = bgWidth/2; //Log.i(TAG, "midX= " + midX); MyAnimationTrans mat = null; if(mHasScroll){//滑动事件 Log.i(TAG, "scroll event"); //已滑到0或bgWidth位置,判断是否须要发送状态改变通知,再也不重画MySwitch区域图像 if(endX >= bgWidth || endX <= 0){ isChange = startX>midX ? endX<=0 : endX>=bgWidth; mState = isChange? endX>=bgWidth : mState; postStateChanged(); mHasScroll = false; return true; } //图片滑向的终点位置 int toX = endX < midX ? 0 : bgWidth; mat = new MyAnimationTrans(endX, toX); } else{//点击事件 Log.i(TAG, "click event"); if(mState){ //点击处在关闭区域,滑向0位置 if(endX < midX){ mat = new MyAnimationTrans(endX, 0); } }else{ //点击处在打开区域,滑向bgWidth位置 if(endX > midX){ mat = new MyAnimationTrans(endX, bgWidth); } } } //处理状态改变响应事件 isChange = mState ? endX<midX : endX>midX; mState = isChange ? endX > midX : mState; postStateChanged(); //将滑动标志更改成false mHasScroll = false; if(mat != null){ this.invalidate(); new Thread(mat).start(); } break; default: break; } return true; } //状态是否发生改变,true为发生改变,false为无 private boolean isChange = false; /** * 响应状态改变监听事件 */ private void postStateChanged(){ //通知状态改变监听接口 if(isChange && msl != null){ msl.onMySwicthChanged(this); isChange = false; Log.i(TAG, "ACTION_UP,发送状态改变事件,将isChange置为false!"); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //关闭状态 if(currState == STATE_OFF){ drawBitmap(canvas, null, null, bitmapOFF); drawBitmap(canvas, null, null, bitmapThumb); } //打开状态 else if(currState == STATE_ON){ drawBitmap(canvas, null, null, bitmapON); int count = canvas.getSaveCount(); canvas.translate(bgWidth - thumbWidth, 0); drawBitmap(canvas, null, null, bitmapThumb); canvas.restoreToCount(count); } //滑动状态 else{ int count = canvas.save(); if(currX > bgWidth/2){ drawBitmap(canvas, null, null, bitmapON); Log.i(TAG, "状态改变为打开,mState=" + mState); }else{ drawBitmap(canvas, null, null, bitmapOFF); Log.i(TAG, "状态改变为关闭,mState=" + mState); } canvas.restoreToCount(count); count = canvas.save(); int toX = currX - thumbWidth/2; toX = toX < 0 ? 0 : toX > (bgWidth - thumbWidth) ? (bgWidth - thumbWidth) : toX; //Log.i(TAG, "toX = " + toX); canvas.translate( toX, 0); drawBitmap(canvas, null, null, bitmapThumb); canvas.restoreToCount(count); } } /** * 将图片画到屏幕中 * @param canvasCanvas对象 * @param fromRC初始位置 * @param destRC终点位置 * @param bitmap图片对象 */ private void drawBitmap(Canvas canvas, Rect fromRC, Rect destRC, Bitmap bitmap){ //destRC为null,则从(0,0)位置开始画图 destRC = destRC==null ? new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()) : destRC; Paint paint = new Paint(); canvas.drawBitmap(bitmap, fromRC, destRC, paint); } /** * 自定义动画类 * @author liuwh * */ private class MyAnimationTrans implements Runnable{ private int fromX; private int endX; private static final int SPEED = 5;//默认移动速度 public MyAnimationTrans(final int fromX, final int endX){ this.fromX = fromX; this.endX = endX; } @Override public void run() { int speed = endX > fromX ? SPEED : -SPEED; currX = endX; //是否结束循环标志,true为结束 boolean isEnd = false; while(true){ currX += speed; if(currX > bgWidth || currX < 0){ currX = speed>0 ? bgWidth : 0; isEnd = true;//结束循环 } MySwitch.this.currState = STATE_SCROLL; MySwitch.this.postInvalidate(); if(isEnd){ break; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 状态改变监听 * @author liuwh * */ public interface OnMySwicthStateChangedListener{ void onMySwicthChanged(MySwitch mySwitch); } }