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


    自己定义View基础篇(二)

    自己定义View基础篇(一)

    自己定义View原理

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

    switch

    那来看看真实图片:

    switch

    switch

    假设你要更改样式,请改动例如以下图片:

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

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

    源代码下载

  • 相关阅读:
    mac下mongdb的安装与配置
    zookeeper配置
    差分
    (leetcode)1601.最多可达成的换楼请求
    多线程知识点
    用jQuery中的ajax分页
    Codeforces Round #499 (Div. 1) VP 记录
    Educational Codeforces Round 125 VP 记录
    【笔记】一句话题解
    ABC245 做题记录
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7202137.html
Copyright © 2020-2023  润新知