Android Studio实现滑动图片验证码

源代码链接

效果:

MainActivity

java 复制代码
package com.example.slidingpattern;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {

    //类成员变量
    private SeekBar seekBar;//SeekBar可以供用户进行拖动改变进度值
    private Button button1;
    private SlideImageView slideImageView;//SlideImageView实现随机选取拼图位置,对拼图位置进行验证
    private TextView resultText;
    private View flashView;

    private static final int flashTime = 80;

    private long timeStart = 0;
    private float timeUsed;

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

        seekBar = findViewById(R.id.seekBar1);//滑动条
        button1 = findViewById(R.id.button1);
        slideImageView = findViewById(R.id.slide_image_view);//滑动图片
        flashView = findViewById(R.id.flash_view);
        resultText = findViewById(R.id.show_result);

        slideImageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.background_image));//设置整张图片

        seekBar.setMax(10000);//setMax()方法设置拖动条最大值
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress,
                                          boolean fromUser) {
                //slideImageView 是一个滑动图片视图的实例
                //setMove() 是一个设置移动位置的方法。
                //progress 是一个进度值,根据这个进度值计算出移动距离,并将其乘以 0.0001 作为移动的比例
                slideImageView.setMove(progress*0.0001);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                timeStart = System.currentTimeMillis();//返回当前的计算机时间,
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }

        });

        seekBar.setOnTouchListener(new View.OnTouchListener(){

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //触摸屏幕时刻
                switch(event.getAction()){
                    case MotionEvent.ACTION_UP: //终止触摸时刻
                        timeUsed = (System.currentTimeMillis() - timeStart) / 1000.0f;//(当前计算机时间-开始时间)/1000.0 = 使用时间
                        boolean isTrue = slideImageView.isTrue(0.1);//允许有10%误差
                        if(isTrue) {
                            flashShowAnime();
                            updateText("验证成功,耗时:" + timeUsed + "秒");//更新文本
                        } else {
                            updateText("验证失败");
                        }
                        break;
                }
                return false;
            }

        });

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                reInit();
            }
        });
    }

    //更新文字内容
    private void updateText(final String s) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                resultText.setText(s);
            }
        });
    }

    //初始化页面
    private void reInit() {
        slideImageView.setReDraw();//重新绘制滑动图片视图
        seekBar.setProgress(0);//将滑块的位置重置到起始位置
        resultText.setText("");//清空显示结果的文本框
        flashView.setVisibility(View.INVISIBLE);//将 flashView 的可见性设置为不可见
    }


    private void flashShowAnime() {
        //通过设置参数来定义了一个位移动画
        TranslateAnimation translateAnimation = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF, 1f,//X轴初始位置
                Animation.RELATIVE_TO_SELF, -1f,//X轴移动的结束位置
                Animation.RELATIVE_TO_SELF, 0f,//y轴开始位置
                Animation.RELATIVE_TO_SELF, 0f);//y轴移动后的结束位置
        translateAnimation.setDuration(flashTime);//设置了动画的持续时间,其中 flashTime 是一个变量,用于表示动画持续的时间长度
        //translateAnimation.setInterpolator(new LinearInterpolator());
        flashView.setVisibility(View.VISIBLE);//表示视图可见
        flashView.setAnimation(translateAnimation);//在给定的时间内将视图从一个位置平滑地移动到另一个位置
        //设置一个动画监听器
        translateAnimation.setAnimationListener(new Animation.AnimationListener() {
            //动画开始时调用的回调方法
            @Override
            public void onAnimationStart(Animation animation) {

            }

            //动画结束时调用的回调方法
            @Override
            public void onAnimationEnd(Animation animation) {
                flashView.setVisibility(View.INVISIBLE);//表示视图不可见,但仍占用布局空间
            }

            //动画重复播放时调用的回调方法
            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }

}

SlideImageView

java 复制代码
package com.example.slidingpattern;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import java.util.Random;

public class SlideImageView extends View {

    Bitmap bitmap;
    Bitmap drawBitmap;
    Bitmap verifyBitmap;
    boolean reset = true;

    // 拼图的位置
    int x;
    int y;
    // 验证的地方
    int left, top, right, bottom;
    // 移动x坐标
    int moveX;
    // x坐标最大移动长度
    int moveMax;
    // 正确的拼图x坐标
    int trueX;

    public SlideImageView(Context context) {
        super(context);
    }

    public SlideImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (bitmap == null)
            return;

        if (reset) {

            //  背景图
            int width = getWidth();
            int height = getHeight();

            //根据原来的位图创建一个新的位图
            drawBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);


            //验证
            int length = Math.min(width, height);
            length /= 4;//1/4长度

            // 随机选取拼图的位置
            // new Random().nextInt()生成一个介于0和指定值之间的随机整数
            x = new Random().nextInt(width - length * 2) + length;
            y = new Random().nextInt(height - length * 2) + length;

            left = x;
            top = y;
            right = left + length;
            bottom = top + length;

            //验证的图片
            verifyBitmap = Bitmap.createBitmap(drawBitmap, x, y, length, length);

            // 验证图片的最大移动距离
            moveMax = width - length;
            // 正确的验证位置x
            trueX = x;

            reset = false;
        }

        Paint paint = new Paint();//创建一个 Paint 对象,并使用默认的画笔属性
        // 画背景图
        //在画布上绘制背景图 (drawBitmap)。其中的参数 drawBitmap 是要绘制的背景图像,(0, 0) 是背景图像在画布上的左上角坐标
        canvas.drawBitmap(drawBitmap, 0, 0, paint);
        paint.setColor(Color.parseColor("#66000000"));//未拼图位置的颜色设置
        canvas.drawRect(left, top, right, bottom, paint);//画上阴影
        paint.setColor(Color.parseColor("#ffffffff"));
        canvas.drawBitmap(verifyBitmap, moveX, y, paint);//画验证图片

    }

    public void setImageBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

    public void setMove(double precent) {
        if (precent < 0 || precent > 1)
            return;

        moveX = (int) (moveMax * precent);
        invalidate();
    }

    public boolean isTrue(double range) {
        if (moveX > trueX * (1 - range) && moveX < trueX * (1 + range)) {
            return true;
        } else {
            return false;
        }
    }

    public void setReDraw() {
        reset = true;
        invalidate();
    }
}

activity_main.xml

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.slidingpattern.MainActivity"
    android:orientation="vertical">

   <com.example.slidingpattern.SlideImageView
       android:id="@+id/slide_image_view"
       android:layout_width="240dp"
       android:layout_height="240dp"
       android:layout_marginTop="50dp"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"/>

   <View
       android:id="@+id/flash_view"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:visibility="invisible"
       app:layout_constraintLeft_toLeftOf="@id/slide_image_view"
       app:layout_constraintRight_toRightOf="@id/slide_image_view"
       app:layout_constraintTop_toTopOf="@id/slide_image_view"
       app:layout_constraintBottom_toBottomOf="@id/slide_image_view"
       android:background="@color/black"
       />

   <SeekBar
       android:id="@+id/seekBar1"
       android:layout_width="240dp"
       android:layout_height="wrap_content"
       android:layout_marginTop="310dp"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"/>

   <TextView
       android:id="@+id/show_result"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="330dp"
       android:textSize="20sp"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>

   <Button
       android:id="@+id/button1"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="360dp"
       android:text="重新初始化"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
相关推荐
古月居GYH3 小时前
【数据分析】如何在PyCharm中高效配置和使用SQL
ide·sql·pycharm
雨白8 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
这是个栗子9 小时前
【问题解决】VSCode终端中看不到Git-Bash
ide·git·vscode
kk爱闹10 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空11 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭12 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日13 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安13 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑13 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟17 小时前
CTF Web的数组巧用
android