android自定义控件手势密码

来源:互联网 发布:苹果mac下载输入法安装 编辑:程序博客网 时间:2024/06/05 19:23

现在很多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工程了,里面的代码粘贴就能跑,哟。。又十二点了,洗洗睡了。。。

原创粉丝点击