Android自定义控件滑动开关自定义滑动验证

2,010 阅读6分钟

引言

***Android的道路上自己摸索前进已有一段时间了,平时看着一些大神写的博客,收益很多,今日起,在学习的同时也通过博客分享一些自己的所见所得。
本片为小弟的处女作,有不对的地方希望猿友们指出,共同交流。。。***

该篇内容主要实现滑动验证开关的功能,表述能力有限,直接上效果图:
这里写图片描述

实现过程

由于没有阅读过源码,暂不写实现原理,直接从实现步骤开始。源码在最下方。

初始化画笔等基础属性

// 开关控件底部窗体灰色背景的画笔
    private Paint mBottomPaint;
    // 开关控件底部窗体绿色背景的画笔
    private Paint mBottomPaint_Left;
    // 顶部白色滑块的画笔
    private Paint mTopPaint;
    // 提示字体及验证完成的画笔
    private Paint mTextPaint;
    private String textHint = "拖动滑块验证";
    private String textCompleted = "验证成功";
    private float widthSlide = 0; // 滑块的宽度,设置为view宽的 1/5
    private float viewWidth;// 窗体宽度
    private float viewHeight;//窗体高度
    private float mTextHintWidth;// 提示字体的宽度
    private float mTextCompletedWidth;// 验证完成的宽度

    private float currentX = 0; // 当前手指所在位置
    private boolean isSlidding = false; // 是否正在滑动
    private boolean isCompelted = false; // 是否滑动完成
    // 自定义滑动完成的回掉接口,处理相应的逻辑
    private ScrollCompletedListener scrollCompletedListenner;
 /**
     * 初始化一些基本的属性
     */
    private void init() {
        mBottomPaint = new Paint();
        mBottomPaint.setAntiAlias(true);
        mBottomPaint.setColor(Color.GRAY);
        mTopPaint = new Paint();
        mTopPaint.setAntiAlias(true);
        mTopPaint.setColor(Color.WHITE);
        mBottomPaint_Left = new Paint();
        mBottomPaint_Left.setAntiAlias(true);
        mBottomPaint_Left.setColor(Color.GREEN);
        mTextPaint = new TextPaint();
        mTextPaint.setColor(Color.BLACK);
        mTextPaint.setTextSize(45);
        mTextPaint.setAntiAlias(true);

    }

重写onMeasure方法 :

测量模式一共分为:EXACTLY, AT_MOST,UNSPECIFIED;
就只能使用默认的模式,对应在界面引用该自定义View时,
宽高只能设置为match_parent或者精确值,
为了能够使用wrap_content,必须重写该方法。

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSize(widthMeasureSpec),
                measureSize(heightMeasureSpec));
    }
/**
     * 重写该方法  为了测量自定义view的大小,调用时可以使用wrap_content
     * @param measureSpec
     * @return
     */
    private int measureSize(int measureSpec) {
        int size = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            size = specSize;
        } else {
            size = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                size = Math.min(size, specSize);
            }
        }
        return size;
    }

重写onSizeChanged方法

这个方法在onMeasure方法之后运行,重写该方法的目的是为了测量这个View窗体的宽和高,以及在这个方法中获取要显示文本的宽度。

 /**
     * 为了获取窗体的大小,必须在测量之后才能获取,否则为0
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = getWidth();//该自定义view窗体的宽度
        viewHeight = getHeight();//窗体的高度
        widthSlide = viewWidth/5;// 上方白色滑块的宽度
        //提示文字的宽度
        mTextHintWidth = mTextPaint.measureText(textHint);
        mTextCompletedWidth = mTextPaint.measureText(textCompleted);// 验证成功文字的宽度
    }

重写onDraw方法

画出要显示的图形

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 画出底部的灰色矩形背景
        canvas.drawRect(0, 0, viewWidth, viewHeight, mBottomPaint);
        // 当滑块右侧位置滑动到提示字体左侧时去除掉(不再画出)提示字体
        if ((currentX + widthSlide) <= (viewWidth / 2 - mTextHintWidth / 2)) {
            canvas.drawText(textHint, (viewWidth/2 - mTextHintWidth/2),
                    viewHeight/2 + 15, mTextPaint);
        }

        // 根据滑块的坐标位置,画出白色滑块,滑块的坐标根据手触摸到屏幕上坐标位置计算
        canvas.drawRect(currentX, 0, (currentX + widthSlide),
                viewHeight, mTopPaint);
        // 滑块滑向右侧的过程中,滑块左侧显示出绿色
        canvas.drawRect(0, 0, currentX, viewHeight, mBottomPaint_Left);

        /*当滑块滑动到右侧时,画出滑块上的绿色圆环及验证成功字样
        widthSlide:白色滑块的宽度
        */    
 if (isSlidding) {
            canvas.drawCircle((currentX + widthSlide/2), viewHeight/2,
                    widthSlide/4, mBottomPaint_Left);
            mTextPaint.setColor(Color.WHITE);
            mTextPaint.setTextSize(50);
            canvas.drawText(textCompleted, (viewWidth/2 - mTextCompletedWidth/2),
                    viewHeight/2 + 15, mTextPaint);
            isCompelted = true;
            // 设置滑动完成监听
            if (scrollCompletedListenner != null) {
                scrollCompletedListenner.scrollCompleted();
            }
        }

    }

重写onTouchEvent

当手指滑动时,动态的获取手指的位置,手指滑动到哪里,白色滑块就要在哪个位置显示,也就是说,手指滑动的位置就是白色滑块的左侧顶点坐标。

/**
     * 滑动时动态画方块
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 加判断是为了滑动完成时 防止滑块回滑
        if (!isCompelted) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    currentX = event.getX() < 0 ? 0 : event.getX();
                    if (currentX >= (viewWidth - widthSlide)) {
                        currentX = viewWidth - widthSlide;
                    }
                    invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    if (currentX < (viewWidth - widthSlide - 10)) {
                        currentX = 0;
                        isSlidding = false;
                    } else {
                        currentX = viewWidth - widthSlide;
                        isSlidding = true;
                    }
                    invalidate();
                    break;
            }
        }
        return true;
    }

声明接口,在滑动完成时进行回掉

  /**
     * 声明接口是为了验证完成时进行回掉
     */
    public interface ScrollCompletedListener {
        // 滑动完成
        void scrollCompleted();
    }

    /**
     * 设置监听
     * @param scrollCompletedListener
     */
    public void setOnScrollCompletedListener(ScrollCompletedListener
                                                     scrollCompletedListener) {
        this.scrollCompletedListenner = scrollCompletedListener;
    }

完整代码如下

/**
 * Created on 2017/3/29 and 14:25
 * 作者:王金
 * 邮箱:1002811532@qq.com
 */

public class CustomSwitchView extends View {

    // 开关控件底部窗体灰色背景的画笔
    private Paint mBottomPaint;
    // 开关控件底部窗体绿色背景的画笔
    private Paint mBottomPaint_Left;
    // 顶部白色滑块的画笔
    private Paint mTopPaint;
    // 提示字体及验证完成的画笔
    private Paint mTextPaint;
    private String textHint = "拖动滑块验证";
    private String textCompleted = "验证成功";
    private float widthSlide = 0; // 滑块的宽度,设置为view宽的 1/5
    private float viewWidth;// 窗体宽度
    private float viewHeight;//窗体高度
    private float mTextHintWidth;// 提示字体的宽度
    private float mTextCompletedWidth;// 验证完成的宽度

    private float currentX = 0; // 当前手指所在位置
    private boolean isSlidding = false; // 是否正在滑动
    private boolean isCompelted = false; // 是否滑动完成
    // 自定义滑动完成的回掉接口,处理相应的逻辑
    private ScrollCompletedListener scrollCompletedListenner;

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

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

    public CustomSwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化一些基本的属性
     */
    private void init() {
        mBottomPaint = new Paint();
        mBottomPaint.setAntiAlias(true);
        mBottomPaint.setColor(Color.GRAY);
        mTopPaint = new Paint();
        mTopPaint.setAntiAlias(true);
        mTopPaint.setColor(Color.WHITE);
        mBottomPaint_Left = new Paint();
        mBottomPaint_Left.setAntiAlias(true);
        mBottomPaint_Left.setColor(Color.GREEN);
        mTextPaint = new TextPaint();
        mTextPaint.setColor(Color.BLACK);
        mTextPaint.setTextSize(45);
        mTextPaint.setAntiAlias(true);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSize(widthMeasureSpec),
                measureSize(heightMeasureSpec));
    }

    /**
     * 重写该方法  为了测量自定义view的大小,调用时可以使用wrap_content
     * @param measureSpec
     * @return
     */
    private int measureSize(int measureSpec) {
        int size = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            size = specSize;
        } else {
            size = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                size = Math.min(size, specSize);
            }
        }
        return size;
    }

    /**
     * 为了获取窗体的大小,必须在测量之后才能获取,否则为0
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = getWidth();
        viewHeight = getHeight();
        widthSlide = viewWidth/5;
        mTextHintWidth = mTextPaint.measureText(textHint);
        mTextCompletedWidth = mTextPaint.measureText(textCompleted);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 画出底部的灰色矩形背景
        canvas.drawRect(0, 0, viewWidth, viewHeight, mBottomPaint);
        // 当滑块右侧位置滑动到提示字体左侧时去除掉(不再画出)提示字体
        if ((currentX + widthSlide) <= (viewWidth / 2 - mTextHintWidth / 2)) {
            canvas.drawText(textHint, (viewWidth/2 - mTextHintWidth/2),
                    viewHeight/2 + 15, mTextPaint);
        }

        // 根据滑块的坐标位置,画出白色滑块,滑块的坐标根据手触摸到屏幕上坐标位置计算
        canvas.drawRect(currentX, 0, (currentX + widthSlide),
                viewHeight, mTopPaint);
        // 滑块滑向右侧的过程中,滑块左侧显示出绿色
        canvas.drawRect(0, 0, currentX, viewHeight, mBottomPaint_Left);

        // 当滑块滑动到右侧时,画出滑块上的绿色圆环及验证成功字样
        if (isSlidding) {
            canvas.drawCircle((currentX + widthSlide/2), viewHeight/2,
                    widthSlide/4, mBottomPaint_Left);
            mTextPaint.setColor(Color.WHITE);
            mTextPaint.setTextSize(50);
            canvas.drawText(textCompleted, (viewWidth/2 - mTextCompletedWidth/2),
                    viewHeight/2 + 15, mTextPaint);
            isCompelted = true;
            // 设置滑动完成监听
            if (scrollCompletedListenner != null) {
                scrollCompletedListenner.scrollCompleted();
            }
        }

    }

    /**
     * 滑动时动态画方块
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 加判断是为了滑动完成时 防止滑块回滑
        if (!isCompelted) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    currentX = event.getX() < 0 ? 0 : event.getX();
                    if (currentX >= (viewWidth - widthSlide)) {
                        currentX = viewWidth - widthSlide;
                    }
                    invalidate();
                    break;
                case MotionEvent.ACTION_UP:
                    if (currentX < (viewWidth - widthSlide - 10)) {
                        currentX = 0;
                        isSlidding = false;
                    } else {
                        currentX = viewWidth - widthSlide;
                        isSlidding = true;
                    }
                    invalidate();
                    break;
            }
        }
        return true;
    }

    /**
     * 声明接口是为了验证完成时进行回掉
     */
    public interface ScrollCompletedListener {
        // 滑动完成
        void scrollCompleted();
    }

    /**
     * 设置监听
     * @param scrollCompletedListener
     */
    public void setOnScrollCompletedListener(ScrollCompletedListener
                                                     scrollCompletedListener) {
        this.scrollCompletedListenner = scrollCompletedListener;
    }

}