自定義View — 滑動驗證碼

人間煙火氣,最撫凡人心。 — 網(wǎng)絡(luò)

寫在前面

2020大半年沒有更新,2021年初我又來了,回顧一下2020年,年初立下的Flag,也算是完成了幾個,1.多吃蔬菜水果;2.戒煙少喝酒;3.堅持讀書;4.體重回到了大學(xué)畢業(yè)時的水平。還有沒完成的Flag,1.當(dāng)工作任務(wù)很多的時候,沒有抱著學(xué)習(xí)的態(tài)度去工作,而是為了完成任務(wù)而工作,沒有收獲;2.沒有堅持運動;3.偶爾還是不能調(diào)整好心態(tài)。當(dāng)然還有其他的收獲,1.駕照到手;2.買新房,開始還房貸的生活;3.向心儀的女孩子攤牌成功;4.報自考專升本。

現(xiàn)在進入正題,之前做過一個項目,有注冊賬號的功能,但是注冊之前要通過滑塊驗證碼,先來說說設(shè)計原理,云端會返回給我兩張同高不同寬的圖片,一張是有滑動塊缺口的全部圖片,一張是滑動塊圖片,我只需要橫向滑動將橫坐標(biāo)傳回給云端進行驗證即可。這個設(shè)計思路清晰明了,下面來說一下我的實現(xiàn)思路,首先想到的就是自定義View,往自定義View中添加兩個ImageView用來顯示圖片內(nèi)容,其中顯示滑動塊圖片的ImageView可以通過觸摸事件進行橫向移動,起始位置為最左邊,通過調(diào)整顯示滑動塊圖片的ImageView的左邊距達到橫向移動的效果。

具體實現(xiàn)

創(chuàng)建SliderView繼承自FrameLayout,創(chuàng)建兩個ImageView,通過計算手指移動的距離調(diào)整橫向移動。

public class SliderView extends FrameLayout {

    // 背景圖(固定的)
    private ImageView mInvariableView;
    // 滑動塊(可移動)
    private ImageView mVariableView;

    // 觸摸抬起事件監(jiān)聽器
    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) {
        // 創(chuàng)建背景圖控件
        mInvariableView = new ImageView(context);
        mInvariableView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        addView(mInvariableView);

        // 創(chuàng)建滑動塊控件
        mVariableView = new ImageView(context);
        mVariableView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        addView(mVariableView);
    }

    /**
     * 移動滑動塊
     * 原理:通過設(shè)置滑動塊的左邊距達到移動的效果
     * @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:
                // 手下按下時,記錄當(dāng)前位置
                mDownX = (int) event.getX();
                // 同時記錄滑動塊的左邊距
                mVariableLeftMargin = ((MarginLayoutParams) mVariableView.getLayoutParams()).leftMargin;
                break;
            case MotionEvent.ACTION_MOVE:
                // 計算手指移動的距離
                int distance = (int) (event.getX() - mDownX);
                // 如果左邊距不為0,說明在此次ACTON_DOWN事件產(chǎn)生之前,滑動塊已經(jīng)不在初始位置,已經(jīng)滑動過一次或多次,
                // 要將已產(chǎn)生的左邊距加上,避免丟失上一次滑動的距離,導(dǎo)致滑動塊從初始位置移動
                if (mVariableLeftMargin != 0) distance = mVariableLeftMargin + distance;
                // 避免手指移動的距離過大,滑動塊超出背景圖的范圍,保證滑動塊一直在背景圖內(nèi)移動
                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);
    }

    /**
     * 設(shè)置圖片
     * @param invariableBitmap 固定的背景圖
     * @param variableBitmap 滑動塊圖片
     */
    public void setSliderBitmap(Bitmap invariableBitmap, Bitmap variableBitmap) {
        mInvariableView.setImageBitmap(invariableBitmap);
        mVariableView.setImageBitmap(variableBitmap);

        // 計算滑動塊可以移動的范圍
        mVariableDistance = invariableBitmap.getWidth() - variableBitmap.getWidth();
    }

    /**
     * 設(shè)置觸摸抬起事件監(jiān)聽器
     * @param listener 觸摸抬起事件監(jiān)聽器
     */
    public void setOnTouchUpListener(OnTouchUpListener listener) {
        mOnTouchUpListener = listener;
    }

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

如何使用

下面通過一個Demo演示如何使用該自定義View。

1.創(chuàng)建布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <com.demo.slider.SliderView
            android:id="@+id/view_slider"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
2.創(chuàng)建Activity

新建Activity,重寫onCreate函數(shù),調(diào)用setContentView指定布局,初始化View并SliderView設(shè)置監(jiān)聽器和滑動塊圖片。

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() {
        // 這里涉及到內(nèi)部機密,就用兩個本地圖片代替了,放在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) {
        // 在這個回調(diào)里將橫坐標(biāo)傳回給云端,進行驗證
        Log.d(MainActivity.class.getSimpleName(), "onTouchUp : x = " + x);
    }
}

運行效果如下:

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

最后

開篇既然說了關(guān)于個人,那就再說說吧,畢竟要有始有終,我對2020年整體是滿意的,2021年希望能把2020年沒有完成的Flag完成,賺錢多多,成長多多。2020因為疫情注定不平凡,2021年因為疫情或許不能回家過年,希望疫情早日過去,大家都能夠正常生活。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容