android自定义控件手势密码

现在很多app都用到一种安全机制,手势密码,特别是银行相关的app,虽然他也并不是那么安全,但是就是喜欢用。今天来看一个简单而炫酷的手势密码锁,废话不多说,上图上代码。

这里写图片描述

看图说话,想怎么定义就怎么定义,使用起来就是这么任性。。。

这里写图片描述

箭头可以随手指任意旋转,这就是我要的效果

这里写图片描述

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="count" format="integer" />
    <attr name="outerNorColor" format="color" />
    <attr name="innerNorColor" format="color" />
    <attr name="outerPressColor" format="color" />
    <attr name="innerPressColor" format="color" />
    <attr name="outerUpColor" format="color" />
    <attr name="innerUpColor" format="color" />
    <attr name="mlineColor" format="color" />
    <declare-styleable name="LockViewGroup">
        <attr name="count" />
        <attr name="outerNorColor" />
        <attr name="innerNorColor" />
        <attr name="outerPressColor" />
        <attr name="innerPressColor" />
        <attr name="outerUpColor" />
        <attr name="innerUpColor" />
        <attr name="mlineColor" />
    </declare-styleable>

</resources>

将用到的属性定义出来,供用户自己选择定义。

package com.example.apple.Custom;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by apple on 17/9/16.
 */

public class LockItem extends View {

    final String TAG = this.getClass().getSimpleName();

    private Mode states = Mode.NOR;
    private int width, hight;
    private int outerCircleWidth = 2, outerCircleRadius, innerCircleRadius, centerXY;

    //paint
    private Paint mouerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);//去锯齿
    private Paint minnerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    //color
    private int outerNorColor, innerNorColor;
    private int outerPressColor, innerPressColor;
    private int outerUpColor, innerUpColor;

    //Arrow
    private int mArrowline;
    private Path mArrowPath = new Path();
    private int angle = -1000;//角度,这里值给大点,不然可能有问题

    enum Mode {NOR, PRESS, UP}

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

    public LockItem(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LockItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public LockItem(Context context, int outerNorColor, int innerNorColor, int outerPressColor, int innerPressColor, int outerUpColor, int innerUpColor) {
        this(context);
        this.outerNorColor = outerNorColor;
        this.innerNorColor = innerNorColor;
        this.outerPressColor = outerPressColor;
        this.innerPressColor = innerPressColor;
        this.outerUpColor = outerUpColor;
        this.innerUpColor = innerUpColor;
    }

    public int getCenterXY() {
        return centerXY;
    }


    private void init() {
        mouerCirclePaint.setColor(outerNorColor);
        mouerCirclePaint.setStyle(Paint.Style.STROKE);//空心画笔
        mouerCirclePaint.setStrokeWidth(outerCircleWidth);
        minnerCirclePaint.setStyle(Paint.Style.FILL);//实心
        minnerCirclePaint.setColor(innerNorColor);
        mArrowPath.reset();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wsize = MeasureSpec.getSize(widthMeasureSpec);
        int hsize = MeasureSpec.getSize(heightMeasureSpec);
        width = hight = Math.min(wsize, hsize);
        centerXY = width / 2;
        outerCircleRadius = (width - 2) / 2;
        innerCircleRadius = width / 6;
        setMeasuredDimension(width, hight);
        mArrowPath.reset();
        mArrowline = (int) (width * 1.0 / 2 * 0.3);
        //指引三角形箭头
        mArrowPath.moveTo(width / 2 - mArrowline, centerXY - innerCircleRadius - 4);
        mArrowPath.lineTo(width / 2 + mArrowline, centerXY - innerCircleRadius - 4);
        mArrowPath.lineTo(width / 2, (outerCircleRadius - innerCircleRadius) / 3);
        mArrowPath.close();
    }

    public LockItem setMode(Mode mode) {
        states = mode;
        return this;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (states == Mode.NOR) {
            mouerCirclePaint.setColor(outerNorColor);
            minnerCirclePaint.setColor(innerNorColor);
        } else if (states == Mode.PRESS) {
            mouerCirclePaint.setColor(outerPressColor);
            minnerCirclePaint.setColor(innerPressColor);
        } else if (states == Mode.UP) {
            mouerCirclePaint.setColor(outerUpColor);
            minnerCirclePaint.setColor(innerUpColor);
        }

        canvas.drawCircle(centerXY, centerXY, outerCircleRadius, mouerCirclePaint);
        canvas.drawCircle(centerXY, centerXY, innerCircleRadius, minnerCirclePaint);
        if (angle != -1000) {
            canvas.rotate(angle, centerXY, centerXY);
            canvas.drawPath(mArrowPath, mouerCirclePaint);
        }
    }

    public LockItem setAngle(int angle) {
        this.angle = angle;
        return this;
    }


}

LockItem里面定义了枚举状态,根据用户的手指触摸事件修改状态,来控制view的绘制颜色。里面还定义了一个三角箭头,看效果图能看出效果,根据手势去控制箭头的方向,这个是难点,箭头看起来简单,控制起来却不是那么简单,哎,又少了几根头发。。。

package com.example.apple.Custom;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Toast;

import com.example.apple.pullzoom.R;

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

/**
 * Created by apple on 17/9/16.
 */

public class LockViewGroup extends ViewGroup {

    final String TAG = this.getClass().getSimpleName();

    //密码元素的宽高
    private int lockItemWidth;
    //元素的个数
    private int count = 3;
    //装item容器
    private LockItem[] lockItems;
    //每一个item的间隔
    private int spacingWidth;
    private int width, hight;
    private List<Integer> selectLockViews = new ArrayList<>();
    private Integer[] pwd = new Integer[]{1, 4, 7, 8, 9};

    private int donwx, downy, scaledTouchSlop;
    //color
    private int outerNorColor = Color.RED, innerNorColor = Color.RED;
    private int outerPressColor = Color.BLUE, innerPressColor = Color.BLUE;
    private int outerUpColor = Color.YELLOW, innerUpColor = Color.YELLOW;
    private int mlineColor = Color.BLACK;

    private Paint mlinePait = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Path mlinePath = new Path();

    private int tempx, tempy;
    private LockItem lastView;
    private int pathCenterX, pathCenterY, lastPointX, lastPointY;

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

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

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        scaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        mlinePait.setColor(mlineColor);
        mlinePait.setStrokeWidth(30);
        mlinePait.setAlpha(150);
        mlinePait.setStyle(Paint.Style.STROKE);
        mlinePait.setStrokeCap(Paint.Cap.ROUND);
        mlinePait.setStrokeJoin(Paint.Join.ROUND);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs,R.styleable.LockViewGroup,defStyleAttr,0);
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
        int arr = typedArray.getIndex(i);
            switch (arr) {
                case R.styleable.LockViewGroup_count:
                    count = typedArray.getInteger(arr, 3);
                    break;
                case R.styleable.LockViewGroup_outerNorColor:
                    outerNorColor = typedArray.getColor(arr, Color.RED);
                    break;
                case R.styleable.LockViewGroup_innerNorColor:
                    innerNorColor = typedArray.getColor(arr, Color.RED);
                    break;
                case R.styleable.LockViewGroup_outerPressColor:
                    outerPressColor = typedArray.getColor(arr, Color.BLUE);

                    break;
                case R.styleable.LockViewGroup_innerPressColor:
                    innerPressColor = typedArray.getColor(i, Color.BLUE);
                    break;
                case R.styleable.LockViewGroup_outerUpColor:
                    outerUpColor = typedArray.getColor(arr, Color.YELLOW);
                    break;
                case R.styleable.LockViewGroup_innerUpColor:
                    mlineColor = typedArray.getColor(arr, Color.YELLOW);
                    break;
                case R.styleable.LockViewGroup_mlineColor:
                    innerUpColor = typedArray.getColor(arr, Color.YELLOW);
                    break;


            }

        }

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wsize = MeasureSpec.getSize(widthMeasureSpec);
        int hsize = MeasureSpec.getSize(heightMeasureSpec);
        width = hight = Math.min(wsize, hsize);
        lockItemWidth = (2 * width) / (3 * count + 1);// width / (count + (count + 1) / 2);间隔是item宽度的一半
        spacingWidth = lockItemWidth / 2;
        if (lockItems == null) {
            lockItems = new LockItem[count * count];
            removeAllViews();
            for (int i = 0, j = count * count; i < j; i++) {
                LockItem lockItem = new LockItem(getContext(), outerNorColor, innerNorColor, outerPressColor, innerPressColor, outerUpColor, innerUpColor);
                ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(lockItemWidth, lockItemWidth);
                lockItem.setLayoutParams(layoutParams);
                lockItem.setTag(j - i);
                lockItems[i] = lockItem;
                addView(lockItem);
                measureChild(lockItem, lockItemWidth, lockItemWidth);
            }
        }
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int mChildCount = getChildCount();
        long num = (long) Math.sqrt(mChildCount);
        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num; j++) {
                View childAt = getChildAt(--mChildCount);
                childAt.layout(
                        spacingWidth * (j + 1) + j * lockItemWidth,
                        spacingWidth * (i + 1) + i * lockItemWidth,
                        lockItemWidth * (j + 1) + spacingWidth * (j + 1),
                        lockItemWidth * (i + 1) + spacingWidth * (i + 1));
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.drawPath(mlinePath, mlinePait);
        if (pathCenterX > 0 && pathCenterY > 0) {
            canvas.drawLine(pathCenterX, pathCenterY, lastPointX, lastPointY, mlinePait);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        tempx = (int) event.getX();
        tempy = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                reset(tempx, tempy);
                break;
            case MotionEvent.ACTION_MOVE:
                int dy = downy - tempy;
                int dx = donwx - tempx;
                if (Math.abs(dy) > scaledTouchSlop || Math.abs(dx) > scaledTouchSlop) {

                    donwx = tempx;
                    downy = tempy;
                    LockItem child = getChildByXY(tempx, tempy);
                    if (child != null) {
                        if (!selectLockViews.contains(child.getTag())) {
                            selectLockViews.add((Integer) child.getTag());
                            child.setMode(LockItem.Mode.PRESS);
                            pathCenterX = (child.getLeft() + child.getRight()) / 2;
                            pathCenterY = (child.getTop() + child.getBottom()) / 2;
                            if (selectLockViews.size() == 1) {
                                mlinePath.moveTo(pathCenterX, pathCenterY);
                            } else {
                                mlinePath.lineTo(pathCenterX, pathCenterY);
                            }
                            //箭头直接直接指向某一个小圆的圆心
                            changeArraw(child.getLeft() + child.getCenterXY(), child.getTop() + child.getCenterXY());
                            lastView = child;
                            child.invalidate();
                        }
                    }
                }
                //箭头随着手指改变方向
                changeArraw(tempx, tempy);
                lastPointX = tempx;
                lastPointY = tempy;
                break;
            case MotionEvent.ACTION_UP:
                lastView = null;
                pathCenterX = lastPointX;
                pathCenterY = lastPointY;
                if (selectLockViews.size() == 0) return true;
                if (selectLockViews.size() < 4) {
                    Toast.makeText(getContext(), "密码不能少于4个", Toast.LENGTH_LONG).show();
                    reset(tempx, tempy);
                    return true;
                }
                if (checkPwd()) {
                    Toast.makeText(getContext(), "亲,密码正确", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(getContext(), "密码错误", Toast.LENGTH_LONG).show();
                }
                changeMode();
                this.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        reset(tempx, tempy);
                    }
                }, 200);
                break;
        }
        invalidate();
        return true;
    }

    private void changeArraw(int x, int y) {
        if (lastView != null) {
            double v = getAngle(lastView.getLeft() + lastView.getCenterXY(), lastView.getTop() + lastView.getCenterXY(), x, y);
            lastView.setAngle((int) v);
            lastView.invalidate();
        }
    }

    /**
     * up的时候改变状态,更新view,用户可以根据自己的需要增加几个状态,如:密码错误和密码正确的颜色样式
     */
    private void changeMode() {
        if (lockItems == null) return;
        for (int i = 0; i < lockItems.length; i++) {
            lockItems[i].setMode(LockItem.Mode.UP).invalidate();

            if (lockItems[i].getTag() == selectLockViews.get(selectLockViews.size() - 1)) {
                //去掉最后一个箭头
                lockItems[i].setAngle(-1000).invalidate();
            }
        }
    }

    /**
     *检查密码是否正确
     * @return
     */
    private boolean checkPwd() {
        if (selectLockViews.size() != pwd.length) return false;
        for (int i = 0; i < pwd.length; i++) {
            if (selectLockViews.get(i) != pwd[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 重置,将各种状态复原。
     * @param x
     * @param y
     */
    private void reset(int x, int y) {
        selectLockViews.clear();
        mlinePath.reset();
        donwx = x;
        downy = y;
        pathCenterX = lastPointX = 0;
        pathCenterY = lastPointY = 0;
        for (int i = 0; i < lockItems.length; i++) {
            lockItems[i].setMode(LockItem.Mode.NOR).setAngle(-1000).invalidate();
        }
        invalidate();
    }

    /**
     * 通过手指的坐标去检查当前点在哪一个子view上面
     * @param x
     * @param y
     * @return
     */
    private LockItem getChildByXY(int x, int y) {
        if (lockItems == null) return null;
        for (int i = 0; i < lockItems.length; i++) {
            LockItem lockItem = lockItems[i];
            if (positionInView(lockItem, x, y)) {
                return lockItem;
            }
        }
        return null;
    }

    /**
     * 通过view的边界检查x。y是否在内部
     * @param lockItem
     * @param x
     * @param y
     * @return
     */
    private boolean positionInView(LockItem lockItem, int x, int y) {
        if (x > lockItem.getLeft() && x < lockItem.getRight() && y > lockItem.getTop() && y < lockItem.getBottom()) {
            return true;
        }
        return false;
    }

    /**
     * 这个就是根据两个点坐标计算角度,通知箭头的方向。
     * @param px1
     * @param py1
     * @param px2
     * @param py2
     * @return
     */
    double getAngle(int px1, int py1, int px2, int py2) {
        //两点的x、y值
        int x = px2 - px1;
        int y = py2 - py1;
        double hypotenuse = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        //斜边长度
        double sin = x / hypotenuse;
        double radian = Math.asin(sin);
        //求出弧度
        double angle = 180 / (Math.PI / radian);
        //用弧度算出角度
        if (y > 0) {
            angle = 180 - angle;
        }
        return angle;
    }
}

这里面也一样遵循了自定义控件的三部曲,onMeasure,onLayout,draw。onLayout有很多种方式可以控制子view的位置,这里采用的最简单,最易懂的n*n的乘法口诀模式,里面没有太多的复杂逻辑,都是一些细节的东西,我觉得最麻烦的就是计算角度了,数学是体育老师教的,哎。。。

<?xml version="1.0" encoding="utf-8"?>
<com.example.apple.Custom.LockViewGroup 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"
    app:count="5"
    android:background="@android:color/darker_gray"
    android:layout_height="match_parent">

</com.example.apple.Custom.LockViewGroup>

使用很简单的,拿来就直接可以用,我就不放demo工程了,里面的代码粘贴就能跑,哟。。又十二点了,洗洗睡了。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值