自定义View — 滑动验证码

人间烟火气,最抚凡人心。 — 网络

写在前面

2020大半年没有更新,2021年初我又来了,回顾一下2020年,年初立下的Flag,也算是完成了几个,1.多吃蔬菜水果;2.戒烟少喝酒;3.坚持读书;4.体重回到了大学毕业时的水平。还有没完成的Flag,1.当工作任务很多的时候,没有抱着学习的态度去工作,而是为了完成任务而工作,没有收获;2.没有坚持运动;3.偶尔还是不能调整好心态。当然还有其他的收获,1.驾照到手;2.买新房,开始还房贷的生活;3.向心仪的女孩子摊牌成功;4.报自考专升本。

现在进入正题,之前做过一个项目,有注册账号的功能,但是注册之前要通过滑块验证码,先来说说设计原理,云端会返回给我两张同高不同宽的图片,一张是有滑动块缺口的全部图片,一张是滑动块图片,我只需要横向滑动将横坐标传回给云端进行验证即可。这个设计思路清晰明了,下面来说一下我的实现思路,首先想到的就是自定义View,往自定义View中添加两个ImageView用来显示图片内容,其中显示滑动块图片的ImageView可以通过触摸事件进行横向移动,起始位置为最左边,通过调整显示滑动块图片的ImageView的左边距达到横向移动的效果。

具体实现

创建SliderView继承自FrameLayout,创建两个ImageView,通过计算手指移动的距离调整横向移动。

public class SliderView extends FrameLayout {

    // 背景图(固定的)
    private ImageView mInvariableView;
    // 滑动块(可移动)
    private ImageView mVariableView;

    // 触摸抬起事件监听器
    private OnTouchUpListener mOnTouchUpListener;

    // 滑动块的左边距
    private int mVariableLeftMargin;
    // 滑动块可以滑动的范围
    private int mVariableDistance;
    // 记下手指按下的位置
    private int mDownX;

    public SliderView(@NonNull Context context) {
        this(context, null);
    }

    public SliderView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SliderView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public SliderView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    /**
     * 初始化
     * @param context
     */
    private void init(Context context) {
        // 创建背景图控件
        mInvariableView = new ImageView(context);
        mInvariableView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        addView(mInvariableView);

        // 创建滑动块控件
        mVariableView = new ImageView(context);
        mVariableView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        addView(mVariableView);
    }

    /**
     * 移动滑动块
     * 原理:通过设置滑动块的左边距达到移动的效果
     * @param margin 左边距
     */
    private void move(int margin) {
        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) mVariableView.getLayoutParams();
        marginLayoutParams.leftMargin = margin;
        mVariableView.setLayoutParams(marginLayoutParams);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 手下按下时,记录当前位置
                mDownX = (int) event.getX();
                // 同时记录滑动块的左边距
                mVariableLeftMargin = ((MarginLayoutParams) mVariableView.getLayoutParams()).leftMargin;
                break;
            case MotionEvent.ACTION_MOVE:
                // 计算手指移动的距离
                int distance = (int) (event.getX() - mDownX);
                // 如果左边距不为0,说明在此次ACTON_DOWN事件产生之前,滑动块已经不在初始位置,已经滑动过一次或多次,
                // 要将已产生的左边距加上,避免丢失上一次滑动的距离,导致滑动块从初始位置移动
                if (mVariableLeftMargin != 0) distance = mVariableLeftMargin + distance;
                // 避免手指移动的距离过大,滑动块超出背景图的范围,保证滑动块一直在背景图内移动
                if (distance < 0) distance = 0;
                else if (distance > mVariableDistance) distance = mVariableDistance;
                // 开始移动滑动块
                move(distance);
                break;
            case MotionEvent.ACTION_UP:
                if (mOnTouchUpListener == null)
                    throw new NullPointerException("You have to set the OnTouchUpListener!");
                mOnTouchUpListener.onTouchUp(mVariableView.getLeft());
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * 重置滑动块位置
     */
    public void reset() {
        move(0);
    }

    /**
     * 设置图片
     * @param invariableBitmap 固定的背景图
     * @param variableBitmap 滑动块图片
     */
    public void setSliderBitmap(Bitmap invariableBitmap, Bitmap variableBitmap) {
        mInvariableView.setImageBitmap(invariableBitmap);
        mVariableView.setImageBitmap(variableBitmap);

        // 计算滑动块可以移动的范围
        mVariableDistance = invariableBitmap.getWidth() - variableBitmap.getWidth();
    }

    /**
     * 设置触摸抬起事件监听器
     * @param listener 触摸抬起事件监听器
     */
    public void setOnTouchUpListener(OnTouchUpListener listener) {
        mOnTouchUpListener = listener;
    }

    public interface OnTouchUpListener {
        void onTouchUp(int x);
    }
}

如何使用

下面通过一个Demo演示如何使用该自定义View。

1.创建布局



    


2.创建Activity

新建Activity,重写onCreate函数,调用setContentView指定布局,初始化View并SliderView设置监听器和滑动块图片。

public class MainActivity extends AppCompatActivity implements SliderView.OnTouchUpListener {

    private SliderView mSliderView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        // 这里涉及到内部机密,就用两个本地图片代替了,放在drawable资源目录下
        Bitmap invariableBitmap = DrawableUtil.drawableToBitmap(getResources().getDrawable(R.drawable.invariable));
        Bitmap variableBitmap = DrawableUtil.drawableToBitmap(getResources().getDrawable(R.drawable.variable));

        mSliderView = findViewById(R.id.view_slider);
        mSliderView.setOnTouchUpListener(this);
        mSliderView.setSliderBitmap(invariableBitmap, variableBitmap);
    }

    @Override
    public void onTouchUp(int x) {
        // 在这个回调里将横坐标传回给云端,进行验证
        Log.d(MainActivity.class.getSimpleName(), "onTouchUp : x = " + x);
    }
}

运行效果如下:

初始位置.png
匹配位置.png

最后

开篇既然说了关于个人,那就再说说吧,毕竟要有始有终,我对2020年整体是满意的,2021年希望能把2020年没有完成的Flag完成,赚钱多多,成长多多。2020因为疫情注定不平凡,2021年因为疫情或许不能回家过年,希望疫情早日过去,大家都能够正常生活。

你可能感兴趣的:(自定义View — 滑动验证码)