我在解說之前,先來看看效果圖,有圖有真相:(轉換gif圖片效果太差)

那來看看真實圖片:


假設你要更改樣式,請改動例如以下圖片:

switch_ball

switch_bg

switch_black

switch_bottom
我在這里就不反復解說View與ViewGroup的關系,View的繪制流程。假設你對自己定義View還不甚了解。請看上面幾篇文章。
用法
xml文件:
<com.github.ws.switchbuttonview.widget.SwitchButtonView
xmlns:widget="http://schemas.android.com/apk/res-auto"
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
widget:checked="false" />
widget:checked屬性表示默認是關。當然你也能夠設置成true。
Activity文件:
mSwitch= (SwitchButtonView) findViewById(R.id.sbv);
mSwitch.setOnSwitchListener(new SwitchButtonView.onSwitchListener() {
@Override
public void onSwitchChanged(boolean isCheck) {
}
});
繪制流程
自己定義屬性
res/values/attrs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SwitchButtonView">
<attr name="checked" format="boolean"></attr>
</declare-styleable>
</resources>
SwitchButtonView文件:
public SwitchButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SwitchButtonView);
isChecked = ta.getBoolean(R.styleable.SwitchButtonView_checked, false);
ta.recycle();
init(context);
}
onMeasure()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(switchWidth, switchHeight);
}
switchWidth = bitmapBackGround.getWidth();
bitmapBackGround = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg);
在文章后面我會貼出源代碼,以及源代碼地址供大家參考。了解了原理你想設計成什么樣的開關都不是問題。
這個控件並不須要控制其擺放位置,不須要重寫onLayout()方法。
onDraw()方法確定其形狀
手指點擊控件。依據觸摸的狀態來改變Switch的狀態,須要重寫onTouchEvent()方法,而且返回true。有的同學就會問了,問什么返回true呢?返回false不行嗎?請看Android Touch事件傳遞
手指按下MotionEvent.ACTION_DOWN,獲取手指的X坐標(event.getX())。由於Y坐標是不變的。全部我們不須要考慮。
這里要解說下event.getX()。和event.getRawX()。event.getX()是相對於父控件而言。event.getRawX()相對於屏幕而言。切記他們的值普通情況下是不一樣的。當X坐標小於等於0時。小球的X坐標等於0;假設X坐標大於等於(父控件寬度減去小球寬度),那么小球的X坐標等於父控件寬度減去小球寬度。
ballX = touchX = mTouchX;
if (touchX <= 0) { ballX = 0; }
if (touchX >= switchWidth - bitmapBall.getWidth()) { ballX = switchWidth - bitmapBall.getWidth(); }
手指移動MotionEvent.ACTION_MOVE跟手指按下的情況是一樣的。
手指抬起MotionEvent.ACTION_UP的2種情況,手指抬起時X坐標小於父控件的二分之中的一個,Switch處於關閉狀態;X坐標大於父控件的二分之中的一個,Switch處於開啟狀態:
if (touchX >= switchWidth / 2f) {
isChecked = true;
ballX = switchWidth - bitmapBall.getWidth();
} else {
isChecked = false;
ballX = 0;
}
已經獲取到手指的一個狀態,那么依據手指的狀態去繪制小球的位置。
TOUCH_STATE_DOWN。TOUCH_STATE_MOVE是一樣的,小球的位置有三種情況。
if (touchX > 0 && touchX < switchWidth - bitmapBall.getWidth()) { //小球可運動區域
canvas.drawBitmap(bitmapBall, touchX, 0, mPaint);
} else if (touchX <= 0) { //觸摸到父控件之外的左邊
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) { //觸摸到父控件減去小球寬度的右邊
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}
TOUCH_STATE_UP,LEFT_MOST(關閉)狀態下有四種情況,他們各自是:
if (touchX > 0 && touchX < switchWidth / 2) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth / 2 && touchX <= switchWidth) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
} else if (touchX <= 0) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}
處了父控件之外的左邊和右邊之外,還多出了父控件二分之中的一個的左邊和右邊。
RIGHT_MOST(開啟)狀態下小球的位置:
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
接下來在繪制過程中會遇到圖層的問題。還記得開始那張黑黑的圖片嗎。
有關圖層請點擊這里
繪制新的圖層:
canvas.saveLayer(0, 0, switchWidth, switchHeight, null, saveFlags);
然后我們把黑黑的圖片繪制到新的圖層上面,而且取新舊圖層重疊的舊的圖層部分。
canvas.drawBitmap(bitmapBlack, 0, 0, mPaint);
mPaint.setXfermode(pdf);
繼續繪制底部的那張圖片(switch_bottom),依據開,關狀態繪制。
if (isChecked) {
canvas.drawBitmap(bitmapBottom, 0 - (switchWidth - bitmapBall.getWidth() - ballX), 0, mPaint);
} else {
canvas.drawBitmap(bitmapBottom, -(bitmapBottom.getWidth() / 2 - bitmapBall.getWidth() / 2) + ballX, 0, mPaint);
}
最后調用canvas.restore();完畢圖層的繪制。
到這里onDraw()方法解說的幾乎相同了,后面的接口我就不啰嗦了。詳細請看源代碼:
package com.github.ws.switchbuttonview.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.github.ws.switchbuttonview.R;
/** * Created by Administrator on 3/22 0022. */
public class SwitchButtonView extends View {
private Paint mPaint;
//背景
private Bitmap bitmapBackGround;
//小球
private Bitmap bitmapBall;
//底部
private Bitmap bitmapBottom;
//黑色
private Bitmap bitmapBlack;
//取重疊部分
private PorterDuffXfermode pdf;
//開關狀態
private boolean isChecked;
//觸摸X坐標
private int touchX;
//小球X坐標
private int ballX = 0;
//小球運動狀態
private int ballMoveState = LEFT_MOST;
//圖層標識
private int saveFlags;
//switch的寬度
private int switchWidth;
//switch的高度
private int switchHeight;
//最左邊
private static final int LEFT_MOST = 0;
//最右邊
private static final int RIGHT_MOST = 1;
//手指按下
private static final int TOUCH_STATE_DOWN = 2;
//手指移動
private static final int TOUCH_STATE_MOVE = 3;
//手指抬起
private static final int TOUCH_STATE_UP = 4;
private onSwitchListener mListener;
public SwitchButtonView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SwitchButtonView);
isChecked = ta.getBoolean(R.styleable.SwitchButtonView_checked, false);
ta.recycle();
init(context);
}
public SwitchButtonView(Context context) {
this(context, null);
}
private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
pdf = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); //2張重疊 取上面一張重疊部分
saveFlags = 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;
bitmapBackGround = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg);
bitmapBall = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_ball);
bitmapBottom = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bottom);
bitmapBlack = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_black);
switchWidth = bitmapBackGround.getWidth();
switchHeight = bitmapBackGround.getHeight();
//開
if (isChecked) {
ballMoveState = RIGHT_MOST;
ballX = bitmapBackGround.getWidth() - bitmapBall.getWidth();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(switchWidth, switchHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//添加圖層
canvas.saveLayer(0, 0, switchWidth, switchHeight, null, saveFlags);
//底部是黑色圖層
canvas.drawBitmap(bitmapBlack, 0, 0, mPaint);
mPaint.setXfermode(pdf);
if (isChecked) {
canvas.drawBitmap(bitmapBottom, 0 - (switchWidth - bitmapBall.getWidth() - ballX), 0, mPaint);
} else {
canvas.drawBitmap(bitmapBottom, -(bitmapBottom.getWidth() / 2 - bitmapBall.getWidth() / 2) + ballX, 0, mPaint);
}
mPaint.setXfermode(null);
canvas.restore();
ballMoveState(canvas);
}
/** * 滑動狀態繪制 * * @param canvas */
private void ballMoveState(Canvas canvas) {
switch (ballMoveState) {
case TOUCH_STATE_DOWN:
case TOUCH_STATE_MOVE:
if (touchX > 0 && touchX < switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, touchX, 0, mPaint);
} else if (touchX <= 0) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}
break;
case TOUCH_STATE_UP:
case LEFT_MOST:
if (touchX > 0 && touchX < switchWidth / 2) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth / 2 && touchX <= switchWidth) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
} else if (touchX <= 0) {
canvas.drawBitmap(bitmapBall, 0, 0, mPaint);
} else if (touchX >= switchWidth - bitmapBall.getWidth()) {
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
}
break;
case RIGHT_MOST:
canvas.drawBitmap(bitmapBall, switchWidth - bitmapBall.getWidth(), 0, mPaint);
break;
default:
break;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStateChange((int) event.getX(), TOUCH_STATE_DOWN);
break;
case MotionEvent.ACTION_MOVE:
touchStateChange((int) event.getX(), TOUCH_STATE_MOVE);
break;
case MotionEvent.ACTION_UP:
touchStateChange((int) event.getX(), TOUCH_STATE_UP);
break;
default:
break;
}
return true;
}
/** * 觸摸狀態改變 * * @param mTouchX * @param touchState */
private void touchStateChange(int mTouchX, int touchState) {
ballX = touchX = mTouchX;
if (touchX <= 0) {
ballX = 0;
}
if (touchX >= switchWidth - bitmapBall.getWidth()) {
ballX = switchWidth - bitmapBall.getWidth();
}
ballMoveState = touchState;
if (ballMoveState == TOUCH_STATE_UP) { //手指抬起
ballX = 0;
if (touchX >= switchWidth / 2f) {
isChecked = true;
ballX = switchWidth - bitmapBall.getWidth();
} else {
isChecked = false;
}
if (mListener != null) {
mListener.onSwitchChanged(isChecked);
}
}
invalidate();
}
public void setOnSwitchListener(onSwitchListener listener) {
this.mListener = listener;
}
public interface onSwitchListener {
void onSwitchChanged(boolean isCheck);
}
}
