Android自定义View基础篇(三)之SwitchButton开关

自定义View基础篇(二)

自定义View基础篇(一)

自定义View原理

我在讲解之前,先来看看效果图,有图有真相:(转换gif图片效果太差)

Android自定义View基础篇(三)之SwitchButton开关_第1张图片

那来看看真实图片:

Android自定义View基础篇(三)之SwitchButton开关_第2张图片

Android自定义View基础篇(三)之SwitchButton开关_第3张图片

如果你要更改样式,请修改如下图片:

switch

switch_ball

switch

switch_bg

switch

switch_black

switch

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文件:


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