Android自定义手势密码

代码在公司项目中使用没有问题

绘制时

这里写图片描述

绘制后

这里写图片描述

有需求的拿走

先声明一下,圆圈是两个图片 选中和未选中,不是在onDraw()绘制的

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.gq.android.R;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * Android-Lock9View
 * </p>
 * <p>
 * 九宫格锁屏视图组件,提供一个九宫格手势屏锁视图功能。
 * </p>
 * <p>
 * 手势处理和连线绘制都有组件内部完成,你只需要绑定这个组件并设置手势完成后的回调对象即可。
 * </p>
 * <p>
 * 请使用 setCallBack(CallBack callBack) 函数设置回调对象。
 * </p>
 * <p>
 * 需要注意的是,不论如何设置,组件的高度永远等于宽度。
 * </p>
 *
 * @author TakWolf (<a
 *         href="mailto:[email protected]">[email protected]</a>)
 */
public class Lock9View extends ViewGroup {

    private Paint paint;
    private Bitmap bitmap;
    private Canvas canvas;

    private List<Pair<NodeView, NodeView>> lineList;
    private NodeView currentNode;

    private StringBuilder pwdSb;
    private CallBack callBack;

    private Drawable nodeSrc;
    private Drawable nodeOnSrc;

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

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

    public Lock9View(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    private Lock9View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr); // TODO api 21
        initFromAttributes(attrs, defStyleAttr);
    }

    private void initFromAttributes(AttributeSet attrs, int defStyleAttr) {
        final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Lock9View, defStyleAttr, 0);

        nodeSrc = a.getDrawable(R.styleable.Lock9View_nodeSrc);
        nodeOnSrc = a.getDrawable(R.styleable.Lock9View_nodeOnSrc);
        int lineColor = Color.argb(0, 0, 0, 0);
        lineColor = a.getColor(R.styleable.Lock9View_lineColor, lineColor);
        float lineWidth = 20.0f;
        lineWidth = a.getDimension(R.styleable.Lock9View_lineWidth, lineWidth);

        a.recycle();

        paint = new Paint(Paint.DITHER_FLAG);
        paint.setStyle(Style.STROKE);
        paint.setStrokeWidth(lineWidth);
        paint.setColor(lineColor);
        paint.setAntiAlias(true);

        DisplayMetrics dm = getResources().getDisplayMetrics(); // bitmap的宽度是屏幕宽度,足够使用
        bitmap = Bitmap.createBitmap(dm.widthPixels, dm.widthPixels, Bitmap.Config.ARGB_8888);
        canvas = new Canvas();
        canvas.setBitmap(bitmap);

        for (int n = 0; n < 9; n++) {
            NodeView node = new NodeView(getContext(), n + 1);
            addView(node);
        }
        lineList = new ArrayList<>();
        pwdSb = new StringBuilder();

        // 清除FLAG,否则 onDraw() 不会调用,原因是 ViewGroup 默认透明背景不需要调用 onDraw()
        setWillNotDraw(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widthMeasureSpec, widthMeasureSpec); // 我们让高度等于宽度
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (!changed) {
            return;
        }
        int width = right - left;
        int nodeWidth = width / 3;
        int nodePadding = nodeWidth / 6;
        for (int n = 0; n < 9; n++) {
            NodeView node = (NodeView) getChildAt(n);
            int row = n / 3;
            int col = n % 3;
            int l = col * nodeWidth + nodePadding;
            int t = row * nodeWidth + nodePadding;
            int r = col * nodeWidth + nodeWidth - nodePadding;
            int b = row * nodeWidth + nodeWidth - nodePadding;
            node.layout(l, t, r, b);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                NodeView nodeAt = getNodeAt(event.getX(), event.getY());
                if (nodeAt == null && currentNode == null) { // 不需要画线,之前没接触点,当前也没接触点
                    return true;
                } else { // 需要画线
                    clearScreenAndDrawList(); // 清除所有图像,如果已有线,则重新绘制
                    if (currentNode == null) { // 第一个点 nodeAt不为null
                        currentNode = nodeAt;
                        if (null!=currentNode){
                            currentNode.setHighLighted(true);
                            pwdSb.append(currentNode.getNum());
                        }
                    } else if (nodeAt == null || nodeAt.isHighLighted()) { // 已经有点了,当前并未碰触新点
                        // 以currentNode中心和当前触摸点开始画线
                        canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), event.getX(), event.getY(), paint);
                    } else { // 移动到新点
                        canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), nodeAt.getCenterX(), nodeAt.getCenterY(), paint);// 画线
                        nodeAt.setHighLighted(true);
                        Pair<NodeView, NodeView> pair = new Pair<NodeView, NodeView>(currentNode, nodeAt);
                        lineList.add(pair);
                        // 赋值当前的node
                        currentNode = nodeAt;
                        pwdSb.append(currentNode.getNum());
                    }
                    // 通知onDraw重绘
                    invalidate();
                }
                return true;
            case MotionEvent.ACTION_UP:
                // 还没有触摸到点
                if (pwdSb.length() <= 0) {
                    return super.onTouchEvent(event);
                }
                // 回调结果
                if (callBack != null) {
                    callBack.onFinish(pwdSb.toString());
                    pwdSb.setLength(0); // 清空
                }
                // 清空保存点的集合
                currentNode = null;
                lineList.clear();
                clearScreenAndDrawList();
                // 清除高亮
                for (int n = 0; n < getChildCount(); n++) {
                    NodeView node = (NodeView) getChildAt(n);
                    node.setHighLighted(false);
                }
                // 通知onDraw重绘
                invalidate();
                return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 清掉屏幕上所有的线,然后画出集合里面的线
     */
    private void clearScreenAndDrawList() {
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        for (Pair<NodeView, NodeView> pair : lineList) {
            canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(), pair.second.getCenterX(), pair.second.getCenterY(), paint);
        }
    }

    /**
     * 获取Node,返回null表示当前手指在两个Node之间
     */
    private NodeView getNodeAt(float x, float y) {
        for (int n = 0; n < getChildCount(); n++) {
            NodeView node = (NodeView) getChildAt(n);
            if (!(x >= node.getLeft() && x < node.getRight())) {
                continue;
            }
            if (!(y >= node.getTop() && y < node.getBottom())) {
                continue;
            }
            return node;
        }
        return null;
    }

    /**
     * 设置手势结果的回调监听器
     *
     * @param callBack
     */
    public void setCallBack(CallBack callBack) {
        this.callBack = callBack;
    }

    /**
     * 节点描述类
     */
    private class NodeView extends View {

        private int num;
        private boolean highLighted;

        private NodeView(Context context) {
            super(context);
        }

        @SuppressWarnings("deprecation")
        private NodeView(Context context, int num) {
            this(context);
            this.num = num;
            highLighted = false;
            if (nodeSrc == null) {
                setBackgroundResource(0);
            } else {
                setBackgroundDrawable(nodeSrc);
            }
        }

        public boolean isHighLighted() {
            return highLighted;
        }

        @SuppressWarnings("deprecation")
        public void setHighLighted(boolean highLighted) {
            this.highLighted = highLighted;
            if (highLighted) {
                if (nodeOnSrc == null) {
                    setBackgroundResource(0);
                } else {
                    setBackgroundDrawable(nodeOnSrc);
                }
            } else {
                if (nodeSrc == null) {
                    setBackgroundResource(0);
                } else {
                    setBackgroundDrawable(nodeSrc);
                }
            }
        }

        public int getCenterX() {
            return (getLeft() + getRight()) / 2;
        }

        public int getCenterY() {
            return (getTop() + getBottom()) / 2;
        }

        public int getNum() {
            return num;
        }

    }

    /**
     * 结果回调监听器接口
     */
    public interface CallBack {

        void onFinish(String password);

    }

}

LockPatternIndicator 锁屏指示器

就是上面那九个点

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

import com.gq.android.R;

import java.util.List;


/**
 * indicator(three rows and three columns)
 * @author Sym
 */
public class LockPatternIndicator extends View {

    private int width, height;
    private int cellBoxWidth, cellBoxHeight;
    private int radius;
    private int offset = 2;
    private Paint defaultPaint, selectPaint;
    private IndicatorCell[][] mIndicatorCells = new IndicatorCell[3][3];

    private static final String TAG = "LockPatternIndicator";

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

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

    public LockPatternIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.init();
    }

    private void init(){
        initRadius();
        initPaint();
        init9IndicatorCells();
    }

    private void initRadius() {
        this.radius = (this.width - offset*2)/4/2;
        this.cellBoxHeight = (this.height - offset*2)/3;
        this.cellBoxWidth = (this.width - offset*2)/3;
    }

    private void initPaint() {
        defaultPaint = new Paint();
        defaultPaint.setColor(getResources().getColor(R.color.c999999));
        defaultPaint.setStrokeWidth(3.0f);
        defaultPaint.setStyle(Style.STROKE);
        defaultPaint.setAntiAlias(true);

        selectPaint = new Paint();
        selectPaint.setColor(getResources().getColor(R.color.cff3e19));
        selectPaint.setStrokeWidth(3.0f);
        selectPaint.setStyle(Style.FILL);
        selectPaint.setAntiAlias(true);
    }

    /**
     * initialize nine cells
     */
    private void init9IndicatorCells(){
        int distance = this.cellBoxWidth + this.cellBoxWidth/2 - this.radius;
        for(int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                mIndicatorCells[i][j] = new IndicatorCell(distance*j + radius + offset, distance*i + radius + offset, 3*i + j + 1);
            }
        }
    }

    /**
     * set nine indicator cells size
     */
    private void set9IndicatorCellsSize() {
        int distance = this.cellBoxWidth + this.cellBoxWidth/2 - this.radius;
        for(int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                mIndicatorCells[i][j].setX(distance*j + radius + offset);
                mIndicatorCells[i][j].setY(distance*i + radius + offset);
            }
        }
    }

    /**
     * set indicator
     * @param cells
     */
    public void setIndicator(List<Integer> cells) {
        for( int index=0;index<cells.size();index++) {
            for(int i = 0; i < mIndicatorCells.length; i++) {
                for(int j = 0; j < mIndicatorCells[i].length; j++) {
                    if (cells.get(index) == mIndicatorCells[i][j].getIndex()) {
                        mIndicatorCells[i][j].setStatus(IndicatorCell.STATE_CHECK);
                    }
                }
            }
        }
        this.postInvalidate();
    }

    /**
     * set default indicator
     */
    public void setDefaultIndicator() {
        for(int i = 0; i < mIndicatorCells.length; i++) {
            for(int j = 0; j < mIndicatorCells[i].length; j++) {
                mIndicatorCells[i][j].setStatus(IndicatorCell.STATE_NORMAL);
            }
        }
        this.postInvalidate();
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        this.width = getMeasuredWidth();
        this.height = getMeasuredHeight();
        this.initRadius();
        this.set9IndicatorCellsSize();
        this.invalidate();
    }

    /**
     * draw the view to canvas
     * @param canvas
     */
    private void drawToCanvas(Canvas canvas) {
        for(int i = 0; i < mIndicatorCells.length; i++) {
            for(int j = 0; j < mIndicatorCells[i].length; j++) {
                if(mIndicatorCells[i][j].getStatus() == IndicatorCell.STATE_NORMAL) {
                    canvas.drawCircle(mIndicatorCells[i][j].getX(), mIndicatorCells[i][j].getY(), radius, defaultPaint);
                } else if(mIndicatorCells[i][j].getStatus() == IndicatorCell.STATE_CHECK) {
                    canvas.drawCircle(mIndicatorCells[i][j].getX(), mIndicatorCells[i][j].getY(), radius, selectPaint);
                }
            }
        }
    }

    public class IndicatorCell {
        private int x;// the center x of circle
        private int y;// the center y of circle
        private int status = 0;//default
        private int index;// the cell value

        public static final int STATE_NORMAL = 0;
        public static final int STATE_CHECK = 1;

        public IndicatorCell(){}

        public IndicatorCell(int x, int y, int index){
            this.x = x;
            this.y = y;
            this.index = index;
        }

        public int getX(){
            return this.x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY(){
            return this.y;
        }

        public void setY(int y) {
            this.y = y;
        }

        public int getStatus(){
            return this.status;
        }

        public void setStatus(int status){
            this.status = status;
        }

        public int getIndex() {
            return this.index;
        }

        public void setIndex(int index) {
            this.index = index;
        }
    }

}

布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white">

    <com.gq.android.views.Lock9View
        android:id="@+id/lock_9_view"
        android:layout_width="@dimen/px_524"
        android:layout_height="@dimen/px_524"
        android:layout_centerInParent="true"
        app:lineColor="@color/cff3e19"
        app:lineWidth="@dimen/px_4"
        app:nodeOnSrc="@drawable/huizhi"
        app:nodeSrc="@drawable/chushi"/>

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="35dp"
        android:text="请输入原手势密码"
        android:textColor="@color/c333333"
        android:textSize="@dimen/text_size_46_px"  />
    <com.gq.android.views.LockPatternIndicator
    android:id="@+id/lpi_indicator"
        android:visibility="gone"
        android:layout_below="@+id/title"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="@dimen/px_20"
    android:layout_width="@dimen/px_90"
    android:layout_height="@dimen/px_90" />



    <ImageView
        android:id="@+id/back"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/px_46"
        android:layout_marginTop="35dp"
        android:paddingLeft="@dimen/px_15"
        android:paddingRight="@dimen/px_15"
        android:layout_marginLeft="@dimen/px_15"
        android:src="@drawable/fanhui_hui"
        />
    <TextView
        android:id="@+id/tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/lpi_indicator"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        android:text=""
        android:textColor="@color/cff3e19"
        android:textSize="16sp" />

    <TextView
        android:id="@+id/by_login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="@dimen/px_10"
        android:text="通过登录密码修改"
        android:textColor="@color/c54adff"
        android:textSize="16sp"
        android:layout_alignBaseline="@+id/forget_pwd"
        android:layout_alignBottom="@+id/forget_pwd"
        android:layout_alignLeft="@+id/lock_9_view"
        android:layout_alignStart="@+id/lock_9_view" />

    <TextView
        android:id="@+id/forget_pwd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="36dp"
        android:padding="@dimen/px_10"
        android:text="忘记登录密码"
        android:textColor="@color/c54adff"
        android:textSize="16sp"
        android:layout_alignParentBottom="true"
        android:layout_alignRight="@+id/lock_9_view"
        android:layout_alignEnd="@+id/lock_9_view" />

</RelativeLayout>

java使用

  lock9View.setCallBack(new CallBack() {
            @Override
            public void onFinish(String password) {
                  //绘制完成后指示器显示
                  //重置
                  lpi_indicator.setDefaultIndicator();
                  //画轨迹
                if(!TextUtils.isEmpty(password)){
                    List<Integer> ints=new ArrayList<Integer>();
                    for(int i=0;i<password.length();i++){
                        ints.add(Integer.valueOf(password.substring(i,i+1)));
                    }
                    lpi_indicator.setIndicator(ints);
                }
            }
        });

猜你喜欢

转载自blog.csdn.net/u013728021/article/details/78274670