Android 怎么自定义绘制一个切换按钮(三)

76 阅读3分钟

这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

紧接着上一篇文章的内容,已经差不多了,主体的绘制和事件处理都已经做完,剩下的就是对这些的操作的实现和优化了,还有一些对外公开的接口,方法,设置参数等,已经一些数据的存储,恢复等

接下来就是一些设置类的方法了:

 /***
     * 获取状态
     * @param state
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            this.isOpen = bundle.getBoolean("isOpen");
            state = bundle.getParcelable("instanceState");
        }
        super.onRestoreInstanceState(state);
    }

    /***
     * 保存状态
     */
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        bundle.putBoolean("isOpen", this.isOpen);
        return bundle;
    }
     /***
     * 设置是否可用
     * @param enable
     */
    public void setEnable(boolean enable) {
        this.enable = enable;
        initDrawingVal();
        invalidateView();
    }
    /***
     * 设置是否支持滑动
     * @param slideable
     */
    public void setSlideable(boolean slideable) {
        this.slideable = slideable;
    }
     public boolean isEnable() {
        return enable;
    }


    public int getEnableAlpha() {
        return enableAlpha;
    }

    public void setEnableAlpha(int enableAlpha) {
        this.enableAlpha = enableAlpha;
    }

    public int getDisableAlpha() {
        return disableAlpha;
    }

    public void setDisableAlpha(int disableAlpha) {
        this.disableAlpha = disableAlpha;
    }
    
     public boolean isOpen() {
        return isOpen;
    }

    /***
     * 设置是否默认开启
     * @param isOpen
     */
    public void setOpen(boolean isOpen) {
        setOpen(isOpen, false);
    }

    public void setOpen(boolean isOpen, boolean invokListener) {
        this.isOpen = isOpen;
        initDrawingVal();
        invalidateView();
        if (invokListener && phSwitchButtonListener != null)
            phSwitchButtonListener.onStatusChanged(this, isOpen);
    }
    
     public int getOpenColor() {
        return openColor;
    }

    public void setOpenColor(int openColor) {
        this.openColor = openColor;
    }

    public int getCloseColor() {
        return closeColor;
    }

    protected void setCloseColor(int closeColor) {
        this.closeColor = closeColor;
    }

根据业务需要,开放对外的接口,当然也可以设置开关的形状,矩形的,圆形的,逻辑都一样,就是添加一个判断,注释都有写,稍后会把完整的代码贴出来。

总结

自定义view就是需要细致,逻辑清楚,先大体绘制框架,然后进行微调,遇到问题,仔细的调试,修改设置属性,就可以慢慢完整的把功能实现,所有的逻辑都是通过一步步粗写,细调,慢慢堆砌出来的,日积月累,就形成了习惯,也就是我们说的经验。

下面把完整的代码贴出来

public class MySwitchButton extends View {

    /****圆形形状****/
    public static final int SHAPE_CIRCLE = 1;
    /****矩形形状****/
    public static final int SHAPE_RECT = 2;
    private static final int SHAPE_DEFAULT = 1;
    private int openColor = 0xFF1a81d1;//默认打开色值
    private int closeColor = 0xFFD0D0D0;//默认关闭色值
    private int strokeColor;//边框色值
    private int strokeWidth = 3;//边框宽度
    private int strokeRound = 6;//边框的圆角
    private int switchWidth = 100;//开关宽度
    private int switchHeight = 50;//开关高度
    private int rimSize = 3;//边缘间距

    /****是否默认开启****/
    private boolean isOpen;
    /****形状****/
    private int shape;

    /****画笔****/
    private Paint openPaint;
    private Paint closePaint;
    private Paint paintCircle;
    /****形状****/
    private Rect backRect;
    private Rect frontRect;
    private RectF frontCircleRect;
    private RectF backCircleRect;

    private RectF closeRect;
    private RectF lineCircleRect;

    /****透明度****/
    private int enableAlpha;

    private int disableAlpha;

    private int maxLeft;
    private int minLeft;
    private int frontRectLeft;
    private int frontRectLeftBegin = rimSize;
    private int eventStartX;
    private int eventLastX;
    private int diffX = 0;
    private boolean slideable = true;
    /****切换回调****/
    private MySwitchButtonListener mySwitchButtonListener;

    private boolean enable = true;


    /***
     * 切换接口类
     */
    public interface MySwitchButtonListener {
        void onStatusChanged(MySwitchButton view, boolean isOpen);
    }

    public MySwitchButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MySwitchButton(Context context) {
        this(context, null);
    }

    public MySwitchButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
    }

    /***
     * 初始化属性配置
     * @param context
     * @param attrs
     */
    protected void initAttrs(Context context, @Nullable AttributeSet attrs) {
        openPaint = new Paint();
        closePaint = new Paint();
        paintCircle = new Paint();
        openPaint.setAntiAlias(true);
        closePaint.setAntiAlias(true);
        paintCircle.setAntiAlias(true);
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MySwitchButton);
            shape = typedArray.getInt(R.styleable.PHSwitchButton_switch_shape, SHAPE_DEFAULT);
            slideable = typedArray.getBoolean(R.styleable.PHSwitchButton_switch_slideable, true);
            openColor = typedArray.getColor(R.styleable.MySwitchButton_switch_open_color,openColor);
            closeColor = typedArray.getColor(R.styleable.MySwitchButton_switch_close_color, closeColor);
            enable = typedArray.getBoolean(R.styleable.MySwitchButton_switch_enable, true);
            typedArray.recycle();
        }
        strokeColor = getContext().getResources().getColor(R.color.transparent_10);
    }
    


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = measureDimension(strokeWidth, widthMeasureSpec);
        int height = measureDimension(switchHeight, heightMeasureSpec);
        //如果设置的宽度小于高度,重置宽度为高度的2倍
        if (shape == SHAPE_CIRCLE) {
            if (width < height) width = height * 2;
        }
        setMeasuredDimension(width, height);
        initDrawingVal();
    }

    /***
     * 绘制椭圆
     */
    private void initDrawingVal() {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        backCircleRect = new RectF();
        frontCircleRect = new RectF();
        frontRect = new Rect();
        backRect = new Rect(0, 0, width, height);

        closeRect = new RectF(rimSize, rimSize, width - rimSize, height - rimSize);
        lineCircleRect = new RectF(strokeWidth, strokeWidth, width - strokeWidth, height - strokeWidth);

        minLeft = rimSize;
        if (shape == SHAPE_RECT)
            maxLeft = width / 2;
        else
            maxLeft = width - (height - 2 * rimSize) - rimSize;
        if (isOpen) {
            frontRectLeft = maxLeft;
            enableAlpha = 255;
        } else {
            frontRectLeft = rimSize;
            enableAlpha = 0;
        }
        if (!enable) {
            slideable = false;
            disableAlpha = 80;
        }
        frontRectLeftBegin = frontRectLeft;
    }


    /***
     * 根据mode 重新赋值
     * @param defaultSize
     * @param measureSpec
     * @return
     */
    private int measureDimension(int defaultSize, int measureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = defaultSize;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (shape == SHAPE_RECT) {
            closePaint.setColor(closeColor);
            canvas.drawRect(backRect, closePaint);
            openPaint.setColor(openColor);
            openPaint.setAlpha(enableAlpha);
            canvas.drawRect(backRect, openPaint);
            openPaint.setColor(openColor);
            openPaint.setAlpha(enableAlpha);
            canvas.drawRect(backRect, openPaint);
            frontRect.set(frontRectLeft, rimSize, frontRectLeft + getMeasuredWidth() / 2 - rimSize, getMeasuredHeight() - rimSize);
            paintCircle.setColor(Color.WHITE);
            paintCircle.setShadowLayer(strokeRound, 0, 0, strokeColor);
            canvas.drawRect(frontRect, paintCircle);
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        } else {
            int radius;
            radius = backRect.height() / 2;
            //画关闭
            if (enable) {
                //画关闭背景外框
                closePaint.setColor(strokeColor);
                closePaint.setStyle(Paint.Style.STROKE);
                closePaint.setStrokeWidth(strokeWidth);
                canvas.drawRoundRect(lineCircleRect, radius, radius, closePaint);

                //画关闭背景
                closePaint.setColor(closeColor);
                closePaint.setStyle(Paint.Style.FILL);
                canvas.drawRoundRect(closeRect, radius, radius, closePaint);
            } else {
                //画关闭背景外框
                closePaint.setColor(strokeColor);
                closePaint.setStyle(Paint.Style.STROKE);
                closePaint.setStrokeWidth(strokeWidth);
                canvas.drawRoundRect(lineCircleRect, radius, radius, closePaint);

                //画关闭背景
                closePaint.setColor(closeColor);
                closePaint.setStyle(Paint.Style.FILL);
                canvas.drawRoundRect(closeRect, radius, radius, closePaint);

                //阴影
                closePaint.setColor(Color.WHITE);
                closePaint.setStyle(Paint.Style.FILL);
                backCircleRect.set(backRect);
                closePaint.setAlpha(disableAlpha);
                canvas.drawRoundRect(backCircleRect, radius, radius, closePaint);
            }

            //画开启
            openPaint.setColor(openColor);
            openPaint.setAlpha(enableAlpha);
            backCircleRect.set(backRect);
            canvas.drawRoundRect(backCircleRect, radius, radius, openPaint);
            //画不可用
            openPaint.setColor(Color.WHITE);
            openPaint.setAlpha(disableAlpha);
            canvas.drawRoundRect(backCircleRect, radius, radius, openPaint);
            //画小圆
            frontRect.set(frontRectLeft, rimSize, frontRectLeft + backRect.height() - 2 * rimSize, backRect.height() - rimSize);
            frontCircleRect.set(frontRect);
            paintCircle.setColor(Color.WHITE);
            paintCircle.setShadowLayer(strokeRound, 0, 0, strokeColor);
            canvas.drawRoundRect(frontCircleRect, radius, radius, paintCircle);
            setLayerType(LAYER_TYPE_SOFTWARE, null);

        }
    }

    private boolean isActionMove = false;

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!enable)
            return super.onTouchEvent(event);
        int action = MotionEventCompat.getActionMasked(event);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                eventStartX = (int) event.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (slideable) {
                    eventLastX = (int) event.getRawX();
                    diffX = eventLastX - eventStartX;
                    int tempX = diffX + frontRectLeftBegin;
                    tempX = (tempX > maxLeft ? maxLeft : tempX);
                    tempX = (tempX < minLeft ? minLeft : tempX);
                    if (tempX >= minLeft && tempX <= maxLeft) {
                        frontRectLeft = tempX;
                        enableAlpha = (int) (255 * (float) tempX / (float) maxLeft);
                        isActionMove = true;
                        invalidateView();
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                sliderMove(event);
                break;
            case MotionEvent.ACTION_CANCEL:
                if (isActionMove) {
                    sliderMove(event);
                }
                Log.e("onTouchEvent:", "ACTION_CANCEL");
                break;
            default:
                break;
        }
        return true;
    }

    /***
     * 执行滑动
     * @param event
     */
    private void sliderMove(MotionEvent event) {
        int wholeX = (int) (event.getRawX() - eventStartX);
        frontRectLeftBegin = frontRectLeft;
        boolean toRight;
        toRight = (frontRectLeftBegin > maxLeft / 2 ? true : false);
        if (Math.abs(wholeX) < 3) {
            toRight = !toRight;
        }
        moveToDest(toRight);
    }

    /**
     * 重新绘制
     */
    private void invalidateView() {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            invalidate();
        } else {
            postInvalidate();
        }
    }

    /***
     * 设置监听
     * @param listener
     */
    public void setPHSwitchButtonListener(MySwitchButtonListener listener) {
        this.mySwitchButtonListener = listener;
    }


    public int getOpenColor() {
        return openColor;
    }

    public void setOpenColor(int openColor) {
        this.openColor = openColor;
    }

    public int getCloseColor() {
        return closeColor;
    }

    protected void setCloseColor(int closeColor) {
        this.closeColor = closeColor;
    }

    /***
     * 切换操作
     * @param toRight
     */
    public void moveToDest(final boolean toRight) {
        /****切换动画****/
        ValueAnimator toDestAnim = ValueAnimator.ofInt(frontRectLeft, toRight ? maxLeft : minLeft);
        toDestAnim.setDuration(300);
        toDestAnim.setInterpolator(new AccelerateDecelerateInterpolator());
        toDestAnim.start();
        toDestAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setLayerType(LAYER_TYPE_NONE, null);
                frontRectLeft = (Integer) animation.getAnimatedValue();
                enableAlpha = (int) (255 * (float) frontRectLeft / (float) maxLeft);
                invalidateView();
            }
        });
        toDestAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (toRight) {
                    isOpen = true;
                    frontRectLeftBegin = maxLeft;
                    enableAlpha = 255;
                } else {
                    isOpen = false;
                    frontRectLeftBegin = minLeft;
                    enableAlpha = 0;
                }

                if (mySwitchButtonListener != null)
                    mySwitchButtonListener.onStatusChanged(MySwitchButton.this, isOpen);
            }
        });
    }

    public boolean isOpen() {
        return isOpen;
    }

    /***
     * 设置是否默认开启
     * @param isOpen
     */
    public void setOpen(boolean isOpen) {
        setOpen(isOpen, false);
    }

    public void setOpen(boolean isOpen, boolean invokListener) {
        this.isOpen = isOpen;
        initDrawingVal();
        invalidateView();
        if (invokListener && mySwitchButtonListener != null)
            mySwitchButtonListener.onStatusChanged(this, isOpen);
    }

    /***
     * 设置形状
     * @param shapeType
     */
    public void setShapeType(int shapeType) {
        this.shape = shapeType;
    }

    /***
     * 设置是否支持滑动
     * @param slideable
     */
    public void setSlideable(boolean slideable) {
        this.slideable = slideable;
    }


    public boolean isEnable() {
        return enable;
    }


    public int getEnableAlpha() {
        return enableAlpha;
    }

    public void setEnableAlpha(int enableAlpha) {
        this.enableAlpha = enableAlpha;
    }

    public int getDisableAlpha() {
        return disableAlpha;
    }

    public void setDisableAlpha(int disableAlpha) {
        this.disableAlpha = disableAlpha;
    }

    /***
     * 设置是否可用
     * @param enable
     */
    public void setEnable(boolean enable) {
        this.enable = enable;
        initDrawingVal();
        invalidateView();
    }

    /***
     * 获取状态
     * @param state
     */
    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;
            this.isOpen = bundle.getBoolean("isOpen");
            state = bundle.getParcelable("instanceState");
        }
        super.onRestoreInstanceState(state);
    }

    /***
     * 保存状态
     */
    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        bundle.putBoolean("isOpen", this.isOpen);
        return bundle;
    }
}

资源文件:

<declare-styleable name="MySwitchButton">
    <attr name="switch_shape" format="enum">
        <enum name="CIRCLE" value="1" />
        <enum name="RECT" value="2" />
    </attr>
    <attr name="switch_slideable" format="boolean" />
    <attr name="switch_open" format="boolean" />
    <attr name="switch_open_color" format="color" />
    <attr name="switch_close_color" format="color" />
    <attr name="switch_enable" format="boolean" />
</declare-styleable>