參考文檔: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); } }