九宫格手势密码

本文介绍了如何实现九宫格手势密码的自定义控件,详细讲解了从自定义属性、回调监听到节点状态管理的过程。通过动态添加NodeView并计算布局,结合触摸事件判断,实现手势绘制与识别。完整源码可在CSDN和GitHub获取。

关于自定义控件已经了解过一些理论知识了,但是实际动手的次数实在是很少,最近下定决心开始动手实践。
下面就是对九宫格手势密码的具体实现做一个总结和说明,也为一些不知如何去实现的朋友提供一个思路

首先大家来看一下用法:

<com.example.customer.myapplication.weight.Lock9View
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:errorNum="3"
        app:hightDrawable="@drawable/huizhi"
        app:minPoint="4"
        app:nodeLineColor="@color/colorPrimary"
        app:nodeLineWidth="5dp"
        app:nodePadding="20dp"
        app:normalDrawable="@drawable/chushi"/>

里面app开头的属性都是控件里面的自定义属性,至于自定义属性具体是怎么写的,后面会给大家介绍

接下来我们来看一下控件中提供的一个方法

/**
     * 设置已有密码
     *
     * @param lock
     */
    public void setLock(String lock) {
        this.lock = lock;
        this.isSetLock = false;
    }

这个方法是在已经设置过密码,现在需要去解锁的时候调用的。

至于手势绘制结果是通过一个回调返回的,大家只需要实现一下这个监听就可以监听到密码设置结果和解锁结果了

//密码绘制的回调
    public interface OnLockFinishListener {
        void onSuccess(boolean isSetLock, String password);

        void onShort(int remainder);

        void onFailue(boolean isSetLock, String password);
    }

这个是回调监听,里面主要有三个方法,成功,失败,密码太短这三个,isSetLock是是否是设置密码的,true - 设置密码 false - 解锁密码 password - 当前绘制的密码

接下来我们看一下在Lock9View内部定义的一个继承自View的NodeView

//九宫格中的每个宫格
    private class NodeView extends View {
        //记录对应的中心点坐标,半径,编号,当前状态(默认or高亮)

        private float centerX;

        private float centerY;

        private int number;

        private boolean isNormal;

        public NodeView(Context context) {
            super(context);
            isNormal = true;

            setBackground();
        }

        public NodeView(Context context, int num) {
            this(context);
            this.number = num;
        }

        //设置九宫格的背景色
        private void setBackground() {
            if (isNormal) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    setBackground(nodeNormalDrawable);
                } else {
                    setBackgroundResource(R.drawable.chushi);
                }
            } else {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    setBackground(nodeHightDrawable);
                } else {
                    setBackgroundResource(R.drawable.huizhi);
                }
            }
        }

        public float getCenterX() {
            centerX = (getRight() + getLeft()) / 2;
            return centerX;
        }

        public float getCenterY() {
            centerY = (getBottom() + getTop()) / 2;
            return centerY;
        }

        public int getNumber() {
            return number;
        }

        public boolean isNormal() {
            return isNormal;
        }

        public void setNormal(boolean normal) {
            isNormal = normal;
            setBackground();
        }
    }

这个view主要是为了记录每一个小格格的编码和中心点坐标以及当前状态的
里面主要提供了四个属性centerX和centerY是用来记录中心点坐标的,后面绘制连线的时候要用到
number是自己定义的一个编码,用这个可以很方便的记录手势密码,isNormal是用来实时改变状态的,默认状态以及当我们滑动过程中触摸到这个宫格的时候的状态
具体状态是怎么改变的,是通过内部的setBackground()方法

下面我们就看一下里面具体是怎么实现的

Lock9View直接继承自ViewGroup,所以使用动态添加NodeView的方式把9个宫格添加到Lock9View中

for (int i = 0; i < 9; i++) {
            NodeView nodeView = new NodeView(context, i + 1);
            addView(nodeView);
        }

接下来就需要对子控件的位置进行摆放

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (!changed) {
            return;
        }
        int cCount = getChildCount();
        if (cCount == 0)
            return;
        for (int i = 0; i < cCount; i++) {
            //每个宫格的大小
            int nodeWidth = nodeRadius * 2;
            NodeView nodeView = (NodeView) getChildAt(i);
            //行
            int raw = i / 3;
            //列
            int column = i % 3;
            int left = column * nodeWidth + nodePadding * (column + 1);
            int right = nodeWidth + left;
            int top = raw * nodeWidth + nodePadding * (raw + 1);
            int bottom = nodeWidth + top;
            nodeView.layout(left, top, right, bottom);
        }
    }

动态的计算每一个子控件的左上右下对应坐标

核心功能实现是在接下来的方法里面实现的

@Override
    public boolean onTouchEvent(MotionEvent event) {
        if (errorNum < 1 || (!lock.isEmpty() && lock.equals(currentLock))) {
            //超过错误次数之后就不能继续绘制,如果划出宫格之外也不做处理
            return true;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                //按下和滑动
                //获取正在触摸的是哪个宫格
                NodeView touchNode = getTouchNode(event.getX(), event.getY());
                //如果触摸的超出宫格之外,则不做处理

                //判断这个过程中时候是否触摸到了宫格
                if (touchNode == null && currentNode == null) {
                    //还没有触摸到宫格
                    return true;
                }
                //清空已经绘制的线,并绘制之前已经触摸过的宫格之间的线
                drawTouchedLine();
                if (touchNode != null && currentNode == null) {
                    //首次触摸到宫格,
                    addTouchNodeToList(touchNode);
                } else if (touchNode == null && currentNode != null) {
                    //此时从一个宫格滑动到了宫格之外,此时需要画线
                    if (!isTouchOutside(event.getX(), event.getY()))
                        canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), event.getX(), event.getY(), paint);
                } else if (touchNode != null && currentNode != null) {
                    //从一个宫格滑动到了另一个宫格
                    canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), touchNode.getCenterX(), touchNode.getCenterY(), paint);
                    addTouchNodeToList(touchNode);
                }
                invalidate();
                return true;
            case MotionEvent.ACTION_UP:
                //离开屏幕
                //清除高亮
                for (NodeView nodeView : higtNodeList) {
                    nodeView.setNormal(true);
                }
                //清空之前的绘制内容
                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                //绘制结束的处理
                onTouchFinish();
                return true;
        }
        return super.onTouchEvent(event);
    }

在这段代码中有这么一个方法getTouchNode();,这个方法是用来获取当前触摸的是哪个NodeView,如果返回null,那么还没有触摸到NodeView,我们来看一下里面具体是怎么实现的

 /**
     * 获取当前正在触摸的宫格
     *
     * @return
     */
    private NodeView getTouchNode(float x, float y) {
        int index = -1;
        for (int i = 0; i < getChildCount(); i++) {
            NodeView nodeView = (NodeView) getChildAt(i);
            if (x > nodeView.getCenterX() - nodeRadius && x < nodeView.getCenterX() + nodeRadius && y > nodeView.getCenterY() - nodeRadius && y < nodeView.getCenterY() + nodeRadius) {
                index = i;
                break;
            }
        }
        if (index == -1) {
            Log.e("======index=====", "=====" + index);
            return null;
        }
        return (NodeView) getChildAt(index);
    }

通过轮询九宫格,判断当前触摸的点是否在小宫格的范围之内,是就返回当前的这个NodeView,并结束轮询,否则就返回null

每次滑动过程中都会重新进行绘制,所以我们需要在绘制之前清空之前绘制的内容,并且要绘制出来之前记录下来的已经触摸到的NodeView之间的连线,否则的话只能绘制上一个NodeView和当前触摸点之间的连线
清空和绘制连线的方法是这个

private void drawTouchedLine() {
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        if (higtNodeList.size() >= 2) {
            //至少两个宫格才绘制线
            for (int i = 0; i < higtNodeList.size(); i++) {
                if (i + 1 < higtNodeList.size()) {
                    NodeView first = higtNodeList.get(i);
                    NodeView second = higtNodeList.get(i + 1);
                    canvas.drawLine(first.getCenterX(), first.getCenterY(), second.getCenterX(), second.getCenterY(), paint);
                }
            }
        }
    }

好了,到这里基本上功能都已经实现了,具体的源码大家可以点击下面的链接

CSDN:http://download.csdn.net/download/wrm0013/10121820

GitHub:https://github.com/wrm0013/Lock9View.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值