Android手势密码view学习笔记(二)

我们还是接着我们上一篇博客中的内容往下讲哈,上一节 Android手势密码view笔记(一)我们已经实现了我们的IndicatorView指示器view了:

下面我们来实现下我们的手势密码view:

实现思路:

1、我们照样需要拿到用户需要显示的一些属性(行、列、选中的图片、未选中的图片、错误显示的图片、连接线的宽度跟颜色......)。

2、我们需要根据手势的变换然后需要判断当前手指位置是不是在某个点中,在的话就把该点设置为选中状态,然后每移动到两个点(也就是一个线段)就记录该两个点。

3、最后把记录的所有点(所有线段)画在canvas上,并记录每个点对应的num值(也就是我们设置的密码)。

4、当手指抬起的时候,执行回调方法,把封装的密码集合传给调用着。
好啦~ 既然右了思路,我们就来实现下:

首先是定义一个attrs.xml文件(为了方便,我就直接在上一篇博客中实现的indicatorview的attr里面继续往下定义了):

<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="IndicatorView"> <!--默认状态的drawable--> <attr name="normalDrawable" format="reference" /> <!--被选中状态的drawable--> <attr name="selectedDrawable" format="reference" /> <!--列数--> <attr name="column" format="integer" /> <!--行数--> <attr name="row" format="integer" /> <!--错误状态的drawabe--> <attr name="erroDrawable" format="reference" /> <!--padding值,padding值越大点越小--> <attr name="padding" format="dimension" /> <!--默认连接线颜色--> <attr name="normalStrokeColor" format="color" /> <!--错误连接线颜色--> <attr name="erroStrokeColor" format="color" /> <!--连接线size--> <attr name="strokeWidth" format="dimension" /> </declare-styleable> </resources>

然后就是第一个叫GestureContentView的view去继承viewgroup,并重新三个构造方法:

public class GestureContentView extends ViewGroup { public void setGesturePwdCallBack(IGesturePwdCallBack gesturePwdCallBack) { this.gesturePwdCallBack = gesturePwdCallBack; } public GestureContentView(Context context) { this(context, null); } public GestureContentView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GestureContentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }

然后我们需要在带三个参数的构造方法中获取我们传入的自定义属性:

public GestureContentView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setWillNotDraw(false); obtainStyledAttr(context, attrs, defStyleAttr); initViews(); }

你会发现我这里调用下setWillNotDraw(false);方法,顾名思义,设置为false后onDraw方法才会被调用,当然你也可以重写dispatchDraw方法(具体原因我就不扯了哈,自己网上查或者看view的源码)。

private void obtainStyledAttr(Context context, AttributeSet attrs, int defStyleAttr) { final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.IndicatorView, defStyleAttr, 0); mNormalDrawable = a.getDrawable(R.styleable.IndicatorView_normalDrawable); mSelectedDrawable = a.getDrawable(R.styleable.IndicatorView_selectedDrawable); mErroDrawable = a.getDrawable(R.styleable.IndicatorView_erroDrawable); checkDrawable(); if (a.hasValue(R.styleable.IndicatorView_row)) { mRow = a.getInt(R.styleable.IndicatorView_row, NUMBER_ROW); } if (a.hasValue(R.styleable.IndicatorView_column)) { mColumn = a.getInt(R.styleable.IndicatorView_row, NUMBER_COLUMN); } if (a.hasValue(R.styleable.IndicatorView_padding)) { DEFAULT_PADDING = a.getDimensionPixelSize(R.styleable.IndicatorView_padding, DEFAULT_PADDING); } strokeColor=a.getColor(R.styleable.IndicatorView_normalStrokeColor,DEFAULT_STROKE_COLOR); erroStrokeColor=a.getColor(R.styleable.IndicatorView_erroStrokeColor,ERRO_STROKE_COLOR); strokeWidth=a.getDimensionPixelSize(R.styleable.IndicatorView_strokeWidth,DEFAULT_STROKE_W); }

然后获取到了我们需要的东西后,我们需要知道每个点的大小跟自己的大小了(还是一样的套路,不懂的看上一篇博客哈):

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); float width = MeasureSpec.getSize(widthMeasureSpec); float height = MeasureSpec.getSize(heightMeasureSpec); float result = Math.min(width, height); height = getHeightValue(result, heightMode); width = getWidthValue(result, widthMode); setMeasuredDimension((int) width, (int) height); } private float getHeightValue(float height, int heightMode) { if (heightMode == MeasureSpec.EXACTLY) { mCellHeight = (height - (mColumn + 1) * DEFAULT_PADDING) / mColumn; } else { mCellHeight = Math.min(mNormalDrawable.getIntrinsicHeight(), mSelectedDrawable.getIntrinsicHeight()); height = mCellHeight * mColumn + (mColumn + 1) * DEFAULT_PADDING; } return height; } private float getWidthValue(float width, int widthMode) { if (widthMode == MeasureSpec.EXACTLY) { mCellWidth = (width - (mRow + 1) * DEFAULT_PADDING) / mRow; } else { mCellWidth = Math.min(mNormalDrawable.getIntrinsicWidth(), mSelectedDrawable.getIntrinsicWidth()); width = mCellWidth * mRow + (mRow + 1) * DEFAULT_PADDING; } return width; }

好了,view的大小跟点的大小我们都知道了,然后我们需要根据我们传入的行数跟列数添加我们的点了:

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (!isInitialed && getChildCount() == 0) { isInitialed = true; points = new ArrayList<>(); addChildViews(); } }

首先在onSizeChanged方法中去添加我们的点(也就是子view)因为onSizeChanged会被调很多次,然后避免重复添加子view,我们做了一个判断,第一次并且child=0的时候再去添加子view:

private void addChildViews() { for (int i = 0; i < mRow; i++) { for (int j = 0; j < mColumn; j++) { GesturePoint point = new GesturePoint(); ImageView image = new ImageView(getContext()); point.setImageView(image); int left = (int) ((j + 1) * DEFAULT_PADDING + j * mCellWidth); int top = (int) ((i + 1) * DEFAULT_PADDING + i * mCellHeight); int right = (int) (left + mCellWidth); int bottom = (int) (top + mCellHeight); point.setLeftX(left); point.setRightX(right); point.setTopY(top); point.setBottomY(bottom); point.setCenterX((int) (left + mCellWidth / 2)); point.setCenterY((int) (top + mCellHeight / 2)); point.setNormalDrawable(mNormalDrawable); point.setErroDrawable(mErroDrawable); point.setSelectedDrawable(mSelectedDrawable); point.setState(PointState.POINT_STATE_NORMAL); point.setNum(Integer.parseInt(String.valueOf(mRow * i + j))); point.setPointX(i); point.setPointY(j); this.addView(image, (int) mCellWidth, (int) mCellHeight); points.add(point); } } }

添加的个数=行数*列数,然后把创建的image添加进当前viewgroup中:

for (int i = 0; i < mRow; i++) { for (int j = 0; j < mColumn; j++) { GesturePoint point = new GesturePoint(); ImageView image = new ImageView(getContext()); ...... this.addView(image, (int) mCellWidth, (int) mCellHeight); } }

并且把所有的点信息存放在了一个叫points.add(point);的集合中,集合中存放的是GesturePoint对象:

public class GesturePoint { //点的左边距值 private int leftX; //点的top值 private int topY; //点的右边距值 private int rightX; private int bottomY; //点的中间值x轴 private int centerX; private int centerY; //点对应的行值 private int pointX; //点对应的列值 private int pointY; //点对应的imageview private ImageView imageView; //当前点的状态:选中、未选中、错误 private PointState state; //当前点对应的密码数值 private int num; //未选中点的drawale private Drawable normalDrawable; private Drawable erroDrawable; private Drawable selectedDrawable; }

既然我们已经添加了很多个点,然后我们要做的就是根据行列排列我们点的位置了(重写onLayout方法摆放子view(点)的位置):

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (points != null && points.size() > 0) { for (GesturePoint point : points) { point.layout(); } } } public void layout() { if (this.imageView != null) { this.imageView.layout(leftX, topY, rightX, bottomY); } }

然后我们添加下我们的view并运行代码:

<FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:layout_marginTop="10dp" > <com.leo.library.view.GestureContentView android:id="@+id/id_gesture_pwd" android:layout_gravity="center_horizontal" android:layout_marginTop="10dp" android:layout_width="match_parent" android:layout_height="match_parent" app:column="3" app:row="3" app:padding="50dp" app:normalDrawable="@drawable/gesture_node_normal" app:selectedDrawable="@drawable/gesture_node_pressed" app:erroDrawable="@drawable/gesture_node_wrong" app:normalStrokeColor="#000" app:erroStrokeColor="#ff0000" app:strokeWidth="4dp" /> </FrameLayout>

效果图:

写到这里,离我们的目标越来越近了,接下来要做的就是重写onTouchEvent方法,然后通过手指滑动位置做出相应的改变了:

1、我们需要根据手指的位置查看是否在某个点内,判断该点是不是被选中,没选中就改变其状态为选中状态。

2、手指每连接两个点的时候,我们需要判断两个点中间是否有点,比如:我们手指连接了(0,0) 跟(2,2)这两个点,那么我们需要判断中间是否有点(1、1)存在。然后需要把线段(0,0)~(1、1)和线段(1、1)~(2、2)保存在集合中,然后下一次再画线段的时候起点就为(2、2)点了。

3、没选中一个点我们就把该点对应的num值(密码)存入集合中。

4、当执行ACTION_UP事件(也就是手指抬起的时候),回调方法,把存储的集合数据传给调用者。

@Override public boolean onTouchEvent(MotionEvent event) { //是否允许用户绘制 if (!isDrawEnable) return super.onTouchEvent(event); //画笔颜色设置为绘制颜色 linePaint.setColor(strokeColor); int action = event.getAction(); //当手指按下的时候 if (MotionEvent.ACTION_DOWN == action) { //清除画板 changeState(PointState.POINT_STATE_NORMAL); preX = (int) event.getX(); preY = (int) event.getY(); //根据当前手指位置找出对应的点 currPoint = getPointByPosition(preX, preY); //如果当前手指在某个点中的时候,把该点标记为选中状态 if (currPoint != null) { currPoint.setState(PointState.POINT_STATE_SELECTED); //把当前选中的点添加进集合中 pwds.add(currPoint.getNum()); } //当手指移动的时候 } else if (MotionEvent.ACTION_MOVE == action) { //,清空画板,然后画出前面存储的线段 clearScreenAndDrawLine(); //获取当前移动的位置是否在某个点中 GesturePoint point = getPointByPosition((int) event.getX(), (int) event.getY()); //没有在点的范围内的话并且currpoint也为空的时候(在画板外移动手指)直接返回 if (point == null && currPoint == null) { return super.onTouchEvent(event); } else { //当按下时候的点为空,然后手指移动到了某一点的时候,把该点赋给currpoint if (currPoint == null) { currPoint = point; //修改该点的状态 currPoint.setState(PointState.POINT_STATE_SELECTED); //添加该点的值 pwds.add(currPoint.getNum()); } } //当移动的不在点范围内、一直在同一个点中移动、选中了某个点后再次选中的时候不让选中(也就是不让出现重复密码) if (point == null || currPoint.getNum() == point.getNum() || point.getState() == PointState.POINT_STATE_SELECTED) { lineCanvas.drawLine(currPoint.getCenterX(), currPoint.getCenterY(), event.getX(), event.getY(), linePaint); } else { //修改该点的状态为选中 point.setState(PointState.POINT_STATE_SELECTED); //连接currpoint跟当前point lineCanvas.drawLine(currPoint.getCenterX(), currPoint.getCenterY(), point.getCenterX(), point.getCenterY(), linePaint); //判断两个点中是否存在点 List<Pair<GesturePoint, GesturePoint>> betweenPoints = getBetweenPoints(currPoint, point); //如果存在点的话,把中间点对应的线段存入集合总 if (betweenPoints != null && betweenPoints.size() > 0) { pointPairs.addAll(betweenPoints); currPoint = point; pwds.add(point.getNum()); } else { pointPairs.add(new Pair(currPoint, point)); pwds.add(point.getNum()); currPoint = point; } } invalidate(); } else if (MotionEvent.ACTION_UP == action) { //手指抬起的时候回调 if (gesturePwdCallBack != null) { List<Integer> datas=new ArrayList<>(pwds.size()); datas.addAll(pwds); gesturePwdCallBack.callBack(datas); } } return true; }

重点解释下getBetweenPoints方法(声明一下哈:本人数学比较差,有好方法的童鞋虐过哈。记得评论告诉我一下,拜谢啦~~ 自己觉得自己的方法比较蠢,嘻嘻~!!):

private List<Pair<GesturePoint, GesturePoint>> getBetweenPoints(GesturePoint currPoint, GesturePoint point) { //定义一个集合装传入的点 List<GesturePoint> points1 = new ArrayList<>(); points1.add(currPoint); points1.add(point); //排序两个点 Collections.sort(points1, new Comparator<GesturePoint>() { @Override public int compare(GesturePoint o1, GesturePoint o2) { return o1.getNum() - o2.getNum(); } }); GesturePoint maxPoint = points1.get(1); GesturePoint minPoint = points1.get(0); points1.clear(); /** * 根据等差数列公式an=a1+(n-1)*d,我们知道an跟a1,n=(an的列或者行值-a1的列或者行值+1), * 算出d,如果d为整数那么为等差数列,如果an的列或者行值-a1的列或者行值>1的话,就证明存在 * 中间值。 * 1、算出的d是否为整数 * 2、an的行值-a1的行值>1或者an的列值-a1的列值>1 * * 两个条件成立的话就证明有中间点 */ if (((maxPoint.getNum() - minPoint.getNum()) % Math.max(maxPoint.getPointX(), maxPoint.getPointY()) == 0) && ((maxPoint.getPointX() - minPoint.getPointX()) > 1 || maxPoint.getPointY() - minPoint.getPointY() > 1 )) { //算出等差d int duration = (maxPoint.getNum() - minPoint.getNum()) / Math.max(maxPoint.getPointX(), maxPoint.getPointY()); //算出中间有多少个点 int count = maxPoint.getPointX() - minPoint.getPointX() - 1; count = Math.max(count, maxPoint.getPointY() - minPoint.getPointY() - 1); //利用等差数列公式算出中间点(an=a1+(n-1)*d) for (int i = 0; i < count; i++) { int num = minPoint.getNum() + (i + 1) * duration; for (GesturePoint p : this.points) { //在此判断算出的中间点是否存在并且没有被选中 if (p.getNum() == num && p.getState() != PointState.POINT_STATE_SELECTED) { //把选中的点添加进集合 pwds.add(p.getNum()); //修改该点的状态为选中状态 p.setState(PointState.POINT_STATE_SELECTED); points1.add(p); } } } } //利用算出的中间点来算出中间线段 List<Pair<GesturePoint, GesturePoint>> pairs = new ArrayList<>(); for (int i = 0; i < points1.size(); i++) { GesturePoint p = points1.get(i); if (i == 0) { pairs.add(new Pair(minPoint, p)); } else if (pairs.size() > 0) { pairs.add(new Pair(pairs.get(0).second, p)); } if (i == points1.size() - 1) { pairs.add(new Pair(p, maxPoint)); } } //返回中间线段 return pairs; }

好啦!!代码就解析到这里了哈,看不懂的童鞋自己去拖代码然后跑跑就知道了。

整个做下来除了某几个地方有点难度外,其它的地方也都是很简单的东西呢?以前看起来很高大上的东西是不是现在觉得soeasy了呢?? 哈哈~~~

最后给出项目github链接:
https://github.com/913453448/GestureContentView

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-09-24 06:30:37

Android手势密码view学习笔记(二)的相关文章

Android手势密码view学习笔记(一)

刚接触Android的时候看到别人写的手势密码view,然后当时就在想,我什么时候才能写出如此高端的东西?? 没关系,不要怕哈,说出这样话的人不是你技术不咋地而是你不愿意花时间去研究它,其实也没有那么难哦(世上无难事,只怕有心人!),下面我们就一步一步实现一个手势密码view. 想必都看过手势密码view,但我们还是看看我们今天要实现的效果吧: 上面是一个手势view的提示view,下面是一个手势view. 用法: <com.leo.library.view.GestureContentView

【Android平台】 Alljoyn学习笔记二 编译自带的demo的步骤

BUILDING ANDROID Setup Download the following Android SDKs: Core SDK (release) Onboarding SDK Configuration SDK Notification SDK Control Panel SDK Extract all ZIP files to one directory. Build Samples Note, you may need to adjust the below paths base

Android手势密码的实现_Android

一.大致界面介绍: 图1 图2 图3 图4 图1:手势密码绘制界面 [主要是绘制上方的9个提示图标和9个宫格密码图标] 图2:设置手势密码 [监听手势的输入,TouchEvent的事件处理,获取输入的手势密码,同时显示在上方的提示区域] 图3:再绘制一次,两次密码不一致提示界面 [这里在实现的时候,错误提示文字加了"左右晃动的动画",错误路径颜色标记为红色] 图4:校验手势密码,输入的密码错误,给予红色路径+错误文字提示 二.实现思路: 1. 正上方的提示区域,用一个类(LockInd

android 手势密码 画图 有可以实现的源码

问题描述 android 手势密码 画图 有可以实现的源码 10C android 手势密码 还要用红色的线画出错误的图片,这个效果没有实现. 解决方案 Android招财进宝手势密码的实现Android招财进宝手势密码的实现Android招财进宝手势密码的实现 解决方案二: http://blog.csdn.net/wulianghuan/article/details/40536635 解决方案三: http://www.android100.org/html/201502/13/11838

Android开发艺术探索学习笔记(七)_Android

第七章 Android动画深入分析  Android的动画分为三种:View动画,帧动画,属性动画.帧动画属于View动画. 7.1 View动画 View动画的作用对象是View,共有四种动画效果:平移(Translate),缩放(Scale),旋转(Rotate),透明度(Alpha). 7.1.1 View动画的种类 View动画的保存路径:res/anim/filename.xml.XML格式语法如下: <?xml version="1.0" encoding="

Android开发艺术探索学习笔记(七)

第七章 Android动画深入分析 Android的动画分为三种:View动画,帧动画,属性动画.帧动画属于View动画. 7.1 View动画 View动画的作用对象是View,共有四种动画效果:平移(Translate),缩放(Scale),旋转(Rotate),透明度(Alpha). 7.1.1 View动画的种类 View动画的保存路径:res/anim/filename.xml.XML格式语法如下: <?xml version="1.0" encoding="

C#学习笔记(二)

笔记 C#学习笔记(二) write by cash(天下第七)2002.01.20版权所有,翻录不究cashcao@msn.com 选择 我身上携带着精神.信仰.灵魂 思想.欲望.怪癖.邪念.狐臭它们寄生于我身体的家 我必须平等对待我的每一位客人-----------伊沙:<原则> 我的名字是cash,所以我很功利主义:我的星像是Leo,所以我很大男人主义:我的语言是C#,所以我有点儿拿不定主义. /* 你能看得出来,这不是一篇正规的技术文章,所以若你不小心从里边读到了一个爱情故事,可不要奇

kvm虚拟化学习笔记(二)之linux kvm虚拟机安装

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://koumm.blog.51cto.com/703525/1289627 KVM虚拟化学习笔记系列文章列表 ---------------------------------------- kvm虚拟化学习笔记(一)之kvm虚拟化环境安装http://koumm.blog.51cto.com/703525/1288795 kvm虚拟化学习笔记(二)之linuxkvm虚拟机安装htt

Bootstrap3学习笔记(二)之排版_javascript技巧

在上篇文章给大家介绍了BootStrap3学习笔记(一)之网格系统 对于标题,Bootstrap已经修改了h1--h6的样式,如果需要副标题,还可以在其中使用small标记 <h1>h1. Bootstrap heading <small>Secondary text</small></h1> <h2>h2. Bootstrap heading <small>Secondary text</small></h2>