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

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

(0)

相关推荐

  • Android仿支付宝手势密码解锁功能

    Starting 创建手势密码可以查看 CreateGestureActivity.java 文件. 登陆验证手势密码可以看 GestureLoginActivity.java 文件. Features 使用了 JakeWharton/butterknife butterknife 使用了 ACache 来存储手势密码 /** * 保存手势密码 */ private void saveChosenPattern(List<LockPatternView.Cell> cells) { byte[

  • Android自定义控件实现手势密码

    Android手势解锁密码效果图 首先呢想写这个手势密码的想法呢,完全是凭空而来的,然后笔者就花了一天时间弄出来了.本以为这个东西很简单,实际上手的时候发现,还有很多逻辑需要处理,稍不注意就容易乱套.写个UI效果图大约只花了3个小时,但是处理逻辑就处理了2个小时!废话不多说,下面开始讲解.      楼主呢,自己比较自定义控件,什么东西都掌握在自己的手里感觉那是相当不错(对于赶工期的小伙瓣儿们还是别手贱了,非常容易掉坑),一有了这个目标,我就开始构思实现方式.      1.整个自定义控件是继承

  • Android 简易手势密码开源库详解

    简介 本文介绍一个Android手势密码开源库的使用及实现的详细过程,该开源库主要实现以下几个功能: 支持手势密码的绘制,并支持密码保存功能,解锁时自动比对密码给出结果 封装了绘制密码的方法,比对两次密码是否一致,可以快捷地进行手势密码的设置 可以设置密码输入错误后的重试次数上限 可以自定义不同状态下手势密码图案的颜色 可以自定义手势密码的触摸点数量(n*n) 最近需要用到手势密码解锁功能,找了一些demo感觉用起来都有点麻烦,于是参考一些文章自己造了下轮子,封装了相关的一些方法,使用起来比较便

  • Android自定义UI手势密码改进版

    接着第一个Android UI手势密码设计的基础上继续改进,效果图如下 activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layo

  • Android手势密码的实现

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

  • Android手势密码实现实例代码

    一.效果实现 二.实现思路: 1. 正上方的提示区域,用一个类(LockIndicator.java)来实现,自定义view来绘制9个提示图标: 2. 手势密码绘制区域,用一个类(GestureContentView.java)来实现,它继承自ViewGroup里面, 添加9个ImageView来表示图标, 在onLayout()方法中设置它们的位置: 3. 手势路径绘制, 用一个类(GestureDrawline.java)来实现,复写onTouchEvent()方法,在这个方法里面监听Tou

  • Android自定义UI手势密码改进版源码下载

    在之前文章的铺垫下,再为大家分享一篇:Android手势密码,附源码下载,不要错过. 源码下载:http://xiazai.jb51.net/201610/yuanma/androidLock(jb51.net).rar 先看第一张图片的布局文件 activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://sc

  • Android自定义UI手势密码终结版

    之前写过3篇手势密码的demo,不过没有集成到真实的企业项目中,这几天正好领到一个手势密码项目,昨天刚好弄完,今天抽空整理下,目前还没有完善,有一些地方需要更改,不过基本的流程都可以跑通了. 源码下载地址:http://xiazai.jb51.net/201610/yuanma/AndroidGestureLock(jb51.net).rar 先看主界面的入口把.里面有2个button(一个是设置手势密码.一个是校验手势密码) activity_main.xml <RelativeLayout

  • Android自定义UI手势密码简单版

    先看看效果图: ImageLockActivity package com.example.imagelock; import com.example.view.NinePointLineView; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; public class ImageLockActivity extends Acti

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

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

随机推荐