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

刚接触Android的时候看到别人写的手势密码view,然后当时就在想,我什么时候才能写出如此高端的东西?? 没关系,不要怕哈,说出这样话的人不是你技术不咋地而是你不愿意花时间去研究它,其实也没有那么难哦(世上无难事,只怕有心人!),下面我们就一步一步实现一个手势密码view。

想必都看过手势密码view,但我们还是看看我们今天要实现的效果吧:

上面是一个手势view的提示view,下面是一个手势view。

用法:

 <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"
   />

app打头的是自定义的一些属性,

attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="IndicatorView">
  <!--默认状态的drawable-->
  <attr name="normalDrawable" format="reference" />
  <!--被选中状态的drawable-->
  <attr name="selectedDrawable" format="reference" />
  <!--错误状态的drawabe-->
  <attr name="erroDrawable" format="reference" />
  <!--列数-->
  <attr name="column" format="integer" />
  <!--行数-->
  <attr name="row" format="integer" />
  <!--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>

MainActivity.java:

public class MainActivity extends AppCompatActivity implements IGesturePwdCallBack {
 private GestureContentView mGestureView;
 private IndicatorView indicatorView;
 private TextView tvIndicator;

 private int count=0;
 private String pwd;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mGestureView= (GestureContentView) findViewById(R.id.id_gesture_pwd);
  indicatorView= (IndicatorView) findViewById(R.id.id_indicator_view);
  tvIndicator= (TextView) findViewById(R.id.id_indicator);
  mGestureView.setGesturePwdCallBack(this);
 }

 @Override
 public void callBack(List<Integer> pwds) {
  StringBuffer sbPwd=new StringBuffer();
  for (Integer pwd:pwds) {
   sbPwd.append(pwd);
  }
  tvIndicator.setText(sbPwd.toString());
  if(pwds!=null&&pwds.size()>0){
   indicatorView.setPwds(pwds);
  }
  if(count++==0){
   pwd=sbPwd.toString();
   Toast.makeText(this,"请再次绘制手势密码",Toast.LENGTH_SHORT).show();
   mGestureView.changePwdState(PointState.POINT_STATE_NORMAL,0);
  } else{
   count=0;
   if(pwd.equals(sbPwd.toString())){
    Toast.makeText(this,"密码设置成功",Toast.LENGTH_SHORT).show();
   }else{
    Toast.makeText(this,"两次密码不一致,请重新绘制",Toast.LENGTH_SHORT).show();
    indicatorView.startAnimation(AnimationUtils.loadAnimation(this,R.anim.anim_shake));
    count=0;
    mGestureView.changePwdState(PointState.POINT_STATE_ERRO,0);
    new Handler().postDelayed(new Runnable() {
     @Override
     public void run() {
      mGestureView.changePwdState(PointState.POINT_STATE_NORMAL,0);
     }
    },1000);
   }
  }
 }
}

看不懂也没关系啊,我们先明确下我们要完成的目标,然后一步一步实现:

先实现下我们的指示器view,因为实现了指示器view也就相当于实现了一半的手势密码view了:

实现思路:

1、我们需要知道指示器有多少行、多少列、默认显示什么、选中后显示什么?
2、然后根据传入的密码把对应的点显示成选中状态,没有选中的点为默认状态。

好了,知道我们的思路,首先自定义一个view叫IndicatorView继承view,然后重写三个构造方法:

public class IndicatorView extends View {
 public IndicatorView(Context context) {
  this(context, null);
 }

 public IndicatorView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  }
}

定义自定义属性(在res/values下创建attrs.xml文件):

1、我们需要传入的默认显示图片:

 <!--默认状态的drawable-->
  <attr name="normalDrawable" format="reference" />

2、我们需要拿到传入的选中时图片:

<!--被选中状态的drawable-->
  <attr name="selectedDrawable" format="reference" />

其它的一些属性:

<?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" />
   </declare-styleable>
</resources>

定义完属性后,此时我们xml中就可以引用自定义view了:

<com.leo.library.view.IndicatorView
  android:id="@+id/id_indicator_view"
  android:layout_marginTop="20dp"
  android:layout_width="85dp"
  android:layout_height="85dp"
  android:layout_alignParentTop="true"
  android:layout_centerHorizontal="true"
  app:column="3"
  app:normalDrawable="@drawable/shape_white_indicator"
  app:padding="8dp"
  app:row="3"
  app:selectedDrawable="@drawable/shape_orange_indicator" />

注意:

中间的drawable文件可以在github项目中找到,链接我会在文章最后给出。

有了自定义属性,然后我们在带三个参数的构造方法中获取我们在布局文件传入的自定义属性:

private static final int NUMBER_ROW = 3;
 private static final int NUMBER_COLUMN = 3;
 private int DEFAULT_PADDING = dp2px(10);
 private final int DEFAULT_SIZE = dp2px(40);

 private Bitmap mNormalBitmap;
 private Bitmap mSelectedBitmap;
 private int mRow = NUMBER_ROW;
 private int mColumn = NUMBER_COLUMN;
 public IndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  final TypedArray a = context.obtainStyledAttributes(
    attrs, R.styleable.IndicatorView, defStyleAttr, 0);
  mNormalBitmap = drawableToBitmap(a.getDrawable(R.styleable.IndicatorView_normalDrawable));
  mSelectedBitmap = drawableToBitmap(a.getDrawable(R.styleable.IndicatorView_selectedDrawable));
  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);
  }
 }

好了,现在我们已经拿到了我们想要的东西了,接下来我们需要知道我的view要多大,相比小伙伴都知道接下来要干什么了吧?对~! 我们需要重写下onMeasure方法,然后指定我们view的大小:

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 }

那么我们该以一个什么样的规则指定我们的view的大小呢?

1、当用户自己指定了view的大小的话,我们就用用户传入的size,然后根据传入的宽、高计算出我们的点的大小。

<com.leo.library.view.IndicatorView
  android:id="@+id/id_indicator_view"
  android:layout_marginTop="20dp"
  android:layout_width="85dp"
  android:layout_height="85dp"

2、如果用户没有指定view的大小,宽高都设置为wrap_content的话,我们需要根据用户传入的选中图片跟没选中图片的大小计算view的大小:

android:layout_width="wrap_content"
android:layout_height="wrap_content"

好了,既然知道咋测量我们的view后,我们接下来就实现出来:

@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);
  }
 private float getHeightValue(float height, int heightMode) {
  //当size为确定的大小的话
  //每个点的高度等于(控件的高度-(行数+1)*padding值)/行数
  if (heightMode == MeasureSpec.EXACTLY) {
   mCellHeight = (height - (mRow + 1) * DEFAULT_PADDING) / mRow;
  } else {
   //高度不确定的话,我们就取选中的图片跟未选中图片中的高度的最小值
   mCellHeight = Math.min(mNormalBitmap.getHeight(), mSelectedBitmap.getHeight());
   //此时控件的高度=点的高度*行数+(行数+1)*默认padding值
   height = mCellHeight * mRow + (mRow + 1) * DEFAULT_PADDING;
  }
  return height;
 }

宽度计算方式也是一样的话,只是行数换成了列数:

 private float getWidthValue(float width, int widthMode) {
  if (widthMode == MeasureSpec.EXACTLY) {
   mCellWidth = (width - (mColumn + 1) * DEFAULT_PADDING) / mColumn;
  } else {
   mCellWidth = Math.min(mNormalBitmap.getWidth(), mSelectedBitmap.getWidth());
   width = mCellWidth * mColumn + (mColumn + 1) * DEFAULT_PADDING;
  }
  return width;
 }

好了,现在是知道了点的高度跟宽度,然后控件的宽高自然也就知道了,但是如果我们传入的选中的图片跟未选择的图片大小不一样咋办呢?没关系,接下来我们重新修改下图片的size:

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  .....
  height = getHeightValue(result, heightMode);
  width = getWidthValue(result, widthMode);
  setMeasuredDimension((int) width, (int) height);
  //重新修改图片的size
  resizeBitmap(mCellWidth, mCellHeight);
 }
 private void resizeBitmap(float width, float height) {
  if (width > 0 && height > 0) {
   if (mNormalBitmap.getWidth() != width || mNormalBitmap.getHeight() !=height) {
    if (mNormalBitmap.getWidth() > 0 && mNormalBitmap.getHeight() > 0) {
     mNormalBitmap = Bitmap.createScaledBitmap(mNormalBitmap, (int) width, (int) height, false);
    }
   }
   if (mSelectedBitmap.getWidth()!=width || mSelectedBitmap.getHeight() !=height) {
    if (mSelectedBitmap.getWidth() > 0 && mSelectedBitmap.getHeight() > 0) {
     mSelectedBitmap = Bitmap.createScaledBitmap(mSelectedBitmap, (int) width, (int) height, false);
    }
   }
  }
 }

好了,图片也拿到了,控件的宽高跟点的宽高都知道,所以接下来我们该进入我们的核心代码了(重写onDraw方法,画出我们的点):

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  //遍历行数
  for (int i = 0; i < mRow; i++) {
   //遍历列数
   for (int j = 0; j < mColumn; j++) {
    float left = (j + 1) * DEFAULT_PADDING + j * mCellWidth;
    float top = (i + 1) * DEFAULT_PADDING + i * mCellHeight;
    //每个点代表的密码值=点对应的行数值*列数+对应的列数
    //比如3*3的表格,然后第二排的第一个=1*3+0=3
    int num=i * mColumn + j;
    //此点是不是在传入的密码集合中?
    if (pwds!=null&&pwds.contains(num)) {
     //这个点在传入的密码集合中的话就画一个选中的bitmap
     canvas.drawBitmap(mSelectedBitmap, left, top, null);
    } else {
     canvas.drawBitmap(mNormalBitmap, left, top, null);
    }
   }
  }
 }

嗯嗯!!然后我们暴露一个方法,让外界传入需要现实的密码集合:

 public void setPwds(List<Integer> pwds) {
  if(pwds!=null)this.pwds=pwds;
  if (Looper.myLooper() == Looper.getMainLooper()) {
   invalidate();
  } else {
   postInvalidate();
  }
 }

好啦~~ 我们的指示器view就做完啦~~~ 是不是很简单呢? 指示器view做完后,再想想手势密码view,是不是就只是差根据手势改变,然后画出我们的line呢?

现附上项目的github链接:
https://github.com/913453448/GestureContentView/

下一节我们将一起实现一下手势密码view。

Android手势密码view笔记(二)

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

(0)

相关推荐

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

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

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

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

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

    我们还是接着我们上一篇博客中的内容往下讲哈,上一节 Android手势密码view笔记(一)我们已经实现了我们的IndicatorView指示器view了: 下面我们来实现下我们的手势密码view: 实现思路: 1.我们照样需要拿到用户需要显示的一些属性(行.列.选中的图片.未选中的图片.错误显示的图片.连接线的宽度跟颜色......). 2.我们需要根据手势的变换然后需要判断当前手指位置是不是在某个点中,在的话就把该点设置为选中状态,然后每移动到两个点(也就是一个线段)就记录该两个点. 3.最

  • 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自定义控件实现手势密码

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

  • 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仿支付宝手势密码解锁功能

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

  • 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 简易手势密码开源库详解

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

随机推荐