Android自己定义View基础篇之SwitchButton开关

古人学问无遗力,少壮工夫老始成。这篇文章主要讲述Android自己定义View基础篇之SwitchButton开关相关的知识,希望能为你提供帮助。
自己定义View基础篇(二)
自己定义View基础篇(一)
自己定义View原理
我在解说之前,先来看看效果图,有图有真相:(转换gif图片效果太差)

Android自己定义View基础篇之SwitchButton开关

文章图片

那来看看真实图片:
Android自己定义View基础篇之SwitchButton开关

文章图片

Android自己定义View基础篇之SwitchButton开关

文章图片

假设你要更改样式,请改动例如以下图片:
Android自己定义View基础篇之SwitchButton开关

文章图片

switch_ball
Android自己定义View基础篇之SwitchButton开关

文章图片

switch_bg
Android自己定义View基础篇之SwitchButton开关

文章图片

switch_black
Android自己定义View基础篇之SwitchButton开关

文章图片

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); }}

【Android自己定义View基础篇之SwitchButton开关】源代码下载


    推荐阅读