In the last article, through a simple thinking analysis and code demonstration, finally achieved the effect of custom puzzle captcha. Next, we want to implement the Chinese character click verification code as shown in the following picture:

Analysis of the

The effect diagram can be divided into two parts, the second can be achieved through the conventional combination of layout, we just need to focus on the first, to achieve the first, and then through the combination of layout, the two parts together, the verification code is done.

Part 1 Thinking analysis:

  1. Prepare an image and draw the background using the canvas.drawbitmap () method

  2. Randomly generate 4 coordinate points and write the preset Chinese characters to the sketchboard in turn by using the canva.drawText () method. For randomness here, you have to consider the following:

    • The border. What if the generated left side is at (x, fontSize), the text may be outside the canvas boundary.

Draw the cross-border

  • Overlap. If the first generated coordinate is (100,100), and the second generated coordinate is (100,100), or (101,101), then the two characters are drawn in the same position.

Draw the normal

Draw the overlap

So, in addition to the size of the canvas and text size, calculate a safe area, random points within the safety area, but also save the rendering text area region, for the next to be randomly generated the coordinates of the point, to judge whether the has draw the text area, to avoid repeated drawing text in the same area.

  1. In terms of user interaction, we want to show the order of clicks if they are within the text area, and not handle the clicks if they are not. Here you can click the point to draw a circle background for the center, and then combined with the size of the circle background and the size of the serial number text, calculate the position of the serial number text to be displayed, and try to display it in the center of the circle.
  2. After the interface is drawn, the rest is the logic judgment and callback interface processing, according to the recorded text and click order judgment is ok. Expose the Settings text, hit Refresh, and judge the result callback.

Code implementation

TapVerificationView.java

package com.example.qingfengwei.myapplication;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class TapVerificationView extends View {

    /* Canvas width and height */
    private int width;
    private int height;

    private Bitmap oldBitmap;
    /* Resize the background image according to the prepared image */
    private Bitmap bgBitmap;
    private Paint bgPaint;
    private RectF bgRectF;

    /* Captcha text brush */
    private Paint textPaint;
    private Paint selectPaint;
    private Paint selectTextPaint;
    private List<Region> regions = new ArrayList<Region>();

    private Random random;

    private String fonts = "";
    private int checkCode = 0;


    private List<Point> tapPoints = new ArrayList<Point>();
    private List<Integer> tapIndex = new ArrayList<Integer>();

    private List<Point> textPoints = new ArrayList<Point>();
    private List<Integer> degrees = new ArrayList<Integer>();

    private boolean isInit = true;

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

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

    public TapVerificationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        oldBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.syzt);

        bgPaint = new Paint();
        bgPaint.setAntiAlias(true);
        bgPaint.setFilterBitmap(true);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setFakeBoldText(true);
        textPaint.setColor(Color.parseColor("#AA000000"));
        textPaint.setShadowLayer(3.2.2, Color.RED);
        textPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));

        selectPaint = new Paint();
        selectPaint.setAntiAlias(true);
        selectPaint.setStyle(Paint.Style.FILL);
        selectPaint.setColor(Color.WHITE);


        selectTextPaint = new Paint();

        random = new Random();

        int temp = fonts.length() - 1;
        while (temp > -1) {
            checkCode += temp * Math.pow(10, temp); temp--; }}@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int minimumWidth = getSuggestedMinimumWidth();
        int minimumHeight = getSuggestedMinimumHeight();
        width = measureSize(minimumWidth, widthMeasureSpec);
        height = width;
        bgBitmap = clipBitmap(oldBitmap, width, height);
        bgRectF = new RectF(0.0, width, height);
        textPaint.setTextSize(width / 6);
        setMeasuredDimension(width, height);
    }

    public Bitmap clipBitmap(Bitmap bm, int newWidth, int newHeight) {
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        return Bitmap.createBitmap(bm, 0.0, width, height, matrix, true);
    }

    private int measureSize(int defaultSize, int measureSpec) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        int result = defaultSize;
        switch (mode) {
            case MeasureSpec.UNSPECIFIED:
                result = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = size;
                break;
        }
        return result;
    }

    public static int dp2px(float dp) {
        float density = Resources.getSystem().getDisplayMetrics().density;
        return (int) (density * dp + 0.5 f);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int x = (int) event.getX();
                int y = (int) event.getY();
                for (Region region : regions) {
                    if (region.contains(x, y)) {

                        isInit = false;

                        int index = regions.indexOf(region);
                        if(! tapIndex.contains(index)) { tapIndex.add(index); tapPoints.add(new Point(x, y));
                        }


                        if (tapIndex.size() == fonts.length()) {
                            StringBuilder s = new StringBuilder();
                            for (Integer i : tapIndex) {
                                s.append(i);
                            }
                            int result = Integer.parseInt(s.toString());
                            if (result == checkCode) {
                                handler.sendEmptyMessage(1);
                            } else {
                                handler.sendEmptyMessage(0); } } invalidate(); }}}return false;
    }

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int result = msg.what;
            switch (result) {
                case 1:
                    Toast.makeText(getContext(), "Verification successful!", Toast.LENGTH_SHORT).show();
                    listener.onResult(true);
                    break;
                default:
                    Toast.makeText(getContext(), "Verification failed!", Toast.LENGTH_SHORT).show();
                    postDelayed(new Runnable() {
                        @Override
                        public void run(a) {
                            reDrew();
                            listener.onResult(false); }},1000);
                    break; }}};private boolean checkCover(int x, int y) {
        for (Region region : regions) {
            if (region.contains(x, y)) {
                return true; }}return false;
    }

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

        regions.clear();
        canvas.drawBitmap(bgBitmap, null, bgRectF, bgPaint);

        / * need to draw the user when handling click click on order, this time to judge is to initialize the authentication code, or the user click need to draw click serial number * if verification code to initialize, randomly generated text, draw the text * if the user clicks, click on the order of needs, this time can't again randomly generated coordinates points, To keep the text position unchanged, otherwise there will be a click, a random generation of a captcha situation * */
        if (isInit) {

            textPoints.clear();
            degrees.clear();
            tapIndex.clear();
            tapPoints.clear();

            for (int i = 0; i < fonts.length(); i++) {
                /* The text is written backwards for verification purposes */
                String s = String.valueOf(fonts.charAt(fonts.length() - i - 1));
                int textSize = (int) textPaint.measureText(s);
                canvas.save();
                /* Randomly generate coordinate points */ in the specified range
                int x = random.nextInt(width - textSize);
                int y = random.nextInt(height - textSize);

                /* If it is detected that the point coincides with the text area, then the point coordinates should be randomly generated again. The four conditions here are respectively the positions of the four corners if (x,y) is used to draw the text coordinates * there is a bit of a loop. if it is difficult to understand, it is better to make a sign on the paper */
                while (checkCover(x, y) || checkCover(x, y + textSize) || checkCover(x + textSize, y) || checkCover(x + textSize, y + textSize)) {
                    x = random.nextInt(width - textSize);
                    y = random.nextInt(height - textSize);
                }

                textPoints.add(new Point(x, y));
                canvas.translate(x, y);
                /* Randomly generate an integer of less than 30, skewing the text by */
                int degree = random.nextInt(30);
                degrees.add(degree);
                canvas.rotate(degree);
                canvas.drawText(s, 0, textSize, textPaint);
                regions.add(newRegion(x, y, textSize + x, textSize + y)); canvas.restore(); }}else {
            for (int i = 0; i < fonts.length(); i++) {
                String s = String.valueOf(fonts.charAt(fonts.length() - i - 1));
                int textSize = (int) textPaint.measureText(s);

                canvas.save();

                /* On the renderings, the user clicks on the text will appear the serial number shows that this is the number of clicks, and the verification code text does not change, in fact, the verification code text here is also redrawn, but still the original position, Angle */
                int x = textPoints.get(i).x;
                int y = textPoints.get(i).y;
                int degree = degrees.get(i);
                canvas.translate(x, y);
                canvas.rotate(degree);
                canvas.drawText(s, 0, textSize, textPaint);
                regions.add(new Region(x, y, textSize + x, textSize + y));
                canvas.restore();
            }

            /* Draw the number of clicks */
            for (Point point : tapPoints) {
                int index = tapPoints.indexOf(point) + 1;
                String s = index + "";
                int textSize = width / 6 / 3;
                selectTextPaint.setTextSize(textSize);
                canvas.drawCircle(point.x, point.y, textSize, selectPaint);


                Rect rect = new Rect();
                selectTextPaint.getTextBounds(s, 0.1, rect);

                int textWidth = rect.width();
                int textHeight = rect.height();

                canvas.drawText(s, point.x - textWidth / 2, point.y + textHeight / 2, selectTextPaint); }}}public void reDrew(a) {
        textPoints.clear();
        degrees.clear();
        tapIndex.clear();
        tapPoints.clear();

        isInit = true;

        invalidate();
    }

    public void setVerifyText(String s){
        fonts = s;

        checkCode = 0;
        int temp = fonts.length() - 1;
        while (temp > -1) {
            checkCode += temp * Math.pow(10, temp);
            temp--;
        }

        invalidate();
    }

    private OnVerifyListener listener;

    public void setVerifyListener(OnVerifyListener listener) {
        this.listener = listener; }}Copy the code

Then there’s the composite layout, which is a little simpler. Here’s just the sample code:

dialog_verify.xml


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:gravity="center"
    android:orientation="vertical">

    <com.example.qingfengwei.myapplication.TapVerificationView
        android:id="@+id/tap_verify_view"
        android:layout_width="match_parent"
        android:layout_height="250dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="5dp">

        <TextView
            android:id="@+id/verify_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1" />

        <Button
            android:id="@+id/refresh_verify"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:background="@mipmap/jyw_refresh" />
    </LinearLayout>
</LinearLayout>
Copy the code

VerifyCationDialog.java

package com.example.qingfengwei.myapplication;

import android.app.Dialog;
import android.content.Context;
import android.text.Html;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

import java.util.Random;


public class VerifyCationDialog extends Dialog {

    private int style;
    private TapVerificationView nofTapVerificationView;
    private Button btnRefresh;
    private TextView tvVerifyCode;

    public VerifyCationDialog(Context context, int style) {
        super(context);
        this.style = style;
        init(context);
    }

    private void init(Context context) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        SlidingVerificationView slidingVerificationView = new SlidingVerificationView(context);

        slidingVerificationView.setVerifyListener(new OnVerifyListener() {
            @Override
            public void onResult(boolean isSuccess) {
                if (isSuccess) {
                    dismiss();
                }
                if(listener! =null){ listener.onResult(isSuccess); }}});if (style == 1) {
            setContentView(R.layout.dialog_verify);
            nofTapVerificationView = findViewById(R.id.tap_verify_view);
            btnRefresh = findViewById(R.id.refresh_verify);
            tvVerifyCode = findViewById(R.id.verify_text);

            setVerifyText();

            nofTapVerificationView.setVerifyListener(new OnVerifyListener() {
                @Override
                public void onResult(boolean isSuccess) {
                    if (isSuccess) {
                        dismiss();
                    } else {
                        setVerifyText();
                    }

                    if(listener! =null){ listener.onResult(isSuccess); }}}); btnRefresh.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) { setVerifyText(); nofTapVerificationView.reDrew(); }}); }else {
            setContentView(slidingVerificationView);
        }

        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        int displayWidth = dm.widthPixels;
        int displayHeight = dm.heightPixels;
        WindowManager.LayoutParams p = getWindow().getAttributes();  // Get the current parameter values of the dialog box
        if (displayWidth > displayHeight) {
            if (style == 1) {
                p.width = (int) (displayWidth * 0.4);
            } else {
                p.width = (int) (displayWidth * 0.6); }}else {
            if (style == 1) {
                p.width = (int) (displayWidth * 0.7);
            } else {
                p.width = (int) (displayWidth * 0.9);
            }
        }

        getWindow().setGravity(Gravity.CENTER);
        getWindow().setAttributes(p);
        getWindow().setBackgroundDrawableResource(android.R.color.transparent);
        setCanceledOnTouchOutside(true);
        setCancelable(true);
    }

    private void setVerifyText(a) {
        String baseText = "When you fall in love with each other, you're stuck with the space and the rules of the law.";
        Random random = new Random();
        int start = random.nextInt(baseText.length() - 4 - 1);
        int end = start + 4;
        String verifyText = baseText.substring(start, end);
        nofTapVerificationView.setVerifyText(verifyText);
        tvVerifyCode.setText(Html.fromHtml( + verifyText + "</b></font>"));
    }

    private OnVerifyListener listener;
    public void setVerifyListener(OnVerifyListener listener){
        this.listener = listener; }}Copy the code