热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要为大家详细介绍了Android手势密码view的第二篇学习笔记,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

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

实现思路:

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

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

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

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

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

<&#63;xml version="1.0" encoding="utf-8"&#63;>

 
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
 

然后就是第一个叫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 

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

for (int i = 0; i 

并且把所有的点信息存放在了一个叫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并运行代码:


效果图:

写到这里,离我们的目标越来越近了,接下来要做的就是重写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> 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 datas=new ArrayList<>(pwds.size());
    datas.addAll(pwds);
    gesturePwdCallBack.callBack(datas);
   }
  }
  return true;

 }

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

 private List> getBetweenPoints(GesturePoint currPoint, GesturePoint point) {
  //定义一个集合装传入的点
  List points1 = new ArrayList<>();
  points1.add(currPoint);
  points1.add(point);
  //排序两个点
  Collections.sort(points1, new Comparator() {
   @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 > pairs = new ArrayList<>();
  for (int i = 0; i  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

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


推荐阅读
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • REST与RPC:选择哪种API架构风格?
    在探讨REST与RPC这两种API架构风格的选择时,本文首先介绍了RPC(远程过程调用)的概念。RPC允许客户端通过网络调用远程服务器上的函数或方法,从而实现分布式系统的功能调用。相比之下,REST(Representational State Transfer)则基于资源的交互模型,通过HTTP协议进行数据传输和操作。本文将详细分析两种架构风格的特点、适用场景及其优缺点,帮助开发者根据具体需求做出合适的选择。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 如何高效利用Hackbar插件提升网页调试效率
    通过合理利用Hackbar插件,可以显著提升网页调试的效率。本文介绍了如何获取并使用未包含收费功能的2.1.3版本,以确保在不升级到最新2.2.2版本的情况下,依然能够高效进行网页调试。此外,文章还提供了详细的使用技巧和常见问题解决方案,帮助开发者更好地掌握这一工具。 ... [详细]
  • MySQL 8.0 MGR 自动化部署与配置:DBA 和开源工具的高效解决方案
    MySQL 8.0 MGR 自动化部署与配置:DBA 和开源工具的高效解决方案 ... [详细]
  • 本文介绍了如何在 Windows 系统上利用 Docker 构建一个包含 NGINX、PHP、MySQL、Redis 和 Elasticsearch 的集成开发环境。通过详细的步骤说明,帮助开发者快速搭建和配置这一复杂的技术栈,提升开发效率和环境一致性。 ... [详细]
  • SSAS入门指南:基础知识与核心概念解析
    ### SSAS入门指南:基础知识与核心概念解析Analysis Services 是一种专为决策支持和商业智能(BI)解决方案设计的数据引擎。该引擎能够为报告和客户端应用提供高效的分析数据,并支持在多维数据模型中构建高性能的分析应用。通过其强大的数据处理能力和灵活的数据建模功能,Analysis Services 成为了现代 BI 系统的重要组成部分。 ... [详细]
  • 在Kohana 3框架中,实现最优的即时消息显示方法是许多开发者关注的问题。本文将探讨如何高效、优雅地展示flash消息,包括最佳实践和技术细节,以提升用户体验和代码可维护性。 ... [详细]
  • 深入解析HTTP网络请求API:从基础到进阶的全面指南
    本文全面解析了HTTP网络请求API,从基础到进阶,详细介绍了Android平台上的两种原生API——HttpUrlConnection和HttpClient。这两种API通过对底层Socket的封装,提供了高效、灵活的网络通信功能。文章不仅涵盖了基本的使用方法,还深入探讨了性能优化、错误处理和安全性等方面的高级主题,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • 微信平台通过盛派SDK(sdk.weixin.senparc.com)允许服务号和订阅号使用appId和token读取关注用户的个人信息。然而,这一过程需严格遵守隐私保护和数据安全的相关规定,确保用户数据的安全性和隐私性。 ... [详细]
  • JavaScript XML操作实用工具类:XmlUtilsJS技巧与应用 ... [详细]
  • 本文通过具体实例详细分析了哈希冲突的原因及其潜在影响,并探讨了多种有效的解决策略。研究不仅涵盖了MD5等常用哈希算法的局限性,还提出了基于哈希表扩展、双哈希技术和布隆过滤器等方法的综合解决方案,以提高数据处理的可靠性和效率。 ... [详细]
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • 本文详细介绍了如何安全地手动卸载Exchange Server 2003,以确保系统的稳定性和数据的完整性。根据微软官方支持文档(https://support.microsoft.com/kb833396/zh-cn),在进行卸载操作前,需要特别注意备份重要数据,并遵循一系列严格的步骤,以避免对现有网络环境造成不利影响。此外,文章还提供了详细的故障排除指南,帮助管理员在遇到问题时能够迅速解决,确保整个卸载过程顺利进行。 ... [详细]
author-avatar
张珮娟7063
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有