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工程了,里面的代码粘贴就能跑,哟。。又十二点了,洗洗睡了。。。
阅读全文
1 0
- android自定义控件手势密码
- Android 自定义控件实现手势密码
- Android自定义手势密码
- Android 自定义手势解锁控件
- Android自定义控件--- 手势检测ViewDragHelper
- 自定义手势密码
- 自定义View----Android九宫格手势密码解锁
- 自定义View----Android九宫格手势密码解锁
- 简单的手势密码控件
- android手势密码
- Android 手势密码分析
- Android手势密码
- Android 手势密码
- android 手势密码
- Android手势密码
- Android手势密码
- Android手势密码解锁
- android---手势密码
- Struts+hibernate应用实现页面操作数据库
- shell变量(系统变量和环境变量)
- nlp语义理解的一点儿看法
- C++——左值引用和右值引用
- 阿里、百度、搜狐、优土等互联网公司面试经验总结
- android自定义控件手势密码
- JavaScript基础之Node.js基本模块
- The user operation is waiting for ... "..." to complete
- Base64加密解密
- Halcon单相机标定
- NOIP2015提高组 信息传递
- 基础Fragment文件
- ijkPlayer支持https和增加沉浸式带有so文件,EXOplayer升级到r.2.5.4
- ext6 如何把grid表格的多个数据用json数组传到后台