您的位置:首页>科学 >Android手势密码查看学习笔记(二)

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

2023-10-04 10:01

我们继续上一篇博客的内容吧。在上一节Android手势密码视图笔记(1)中我们已经实现了我们的IndicatorView指示器视图。 :

让我们实现手势密码视图:

实现思路:

1。我们还需要获取用户需要显示的一些属性(行、列、选中的图片、未选中的图片、显示错误的图片、连接线的宽度和颜色……)。

2。我们需要根据手势的变换来判断当前手指位置是否在某个点。如果是,则将该点设置为选中状态,然后每次移动到两个点(即一条线段)。记录下这两点。

3。最后,在画布上绘制所有记录的点(所有线段),并记录每个点对应的num值(即我们设置的密码)。

4。当手指抬起时,执行回调方法并将封装的密码集传递给调用者。
好啦~既然想法正确了,那么我们就来实现一下吧:

第一步定义一个attrs.xml文件(为了方便,我继续直接在上一篇博客实现的indicatorview的attr中定义):
























然后第一个名为GestureContentView的视图继承了viewgroup并创建了三个新的构造方法:

公共类 GestureContentView 扩展 ViewGroup {
公共无效setGesturePwdCallBack(IGesturePwdCallBackgesturePwdCallBack){
this.gesturePwdCallBack =gesturePwdCallBack;
}
公共 GestureContentView(上下文上下文){
这(上下文,空);
}
公共 GestureContentView(上下文上下文,AttributeSet attrs){
这(上下文,属性,0);
}
公共GestureContentView(上下文上下文,AttributeSet attrs,int defStyleAttr){
超级(上下文,defStyleAttr);
}
}

然后我们需要获取我们在构造函数中传入的自定义属性,其中包含三个参数:

公共GestureContentView(上下文上下文,defStyleAttr);
setWillNotDraw(假);
获得StyledAttr(上下文,defStyleAttr);
初始化视图();
}

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

私人无效获得StyledAttr(上下文上下文,int defStyleAttr){
最终 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);
检查Drawable();
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);
笔画宽度=a.getDimensionPixelSize(R.styleable.IndicatorView_笔画宽度,DEFAULT_STROKE_W);
}

得到我们需要的东西后,我们需要知道每个点的大小和我们自己的大小(还是同样的套路,不懂的请看之前的博客):

@覆盖
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
浮动宽度 = MeasureSpec.getSize(widthMeasureSpec);
浮动高度 = MeasureSpec.getSize(heightMeasureSpec);
浮动结果 = Math.min(宽度,高度);
高度 = getHeightValue(结果,heightMode);
宽度 = getWidthValue(结果,widthMode);
setMeasuredDimension((int) 宽度,(int) 高度);
}
私有浮动 getHeightValue(浮动高度,int heightMode){
if (heightMode == MeasureSpec.EXACTLY) {
mCellHeight = (高度 - (mColumn + 1) * DEFAULT_PADDING) / mColumn;
} 别的 {
mCellHeight = Math.min(mNormalDrawable.getIntrinsicHeight(),mSelectedDrawable.getIntrinsicHeight());
高度 = mCellHeight * mColumn + (mColumn + 1) * DEFAULT_PADDING;
}
返回高度;
}
私有浮点 getWidthValue(浮点宽度,int widthMode) {
if (widthMode == MeasureSpec.EXACTLY) {mCellWidth = (宽度 - (mRow + 1) * DEFAULT_PADDING) / mRow;
} 别的 {
mCellWidth = Math.min(mNormalDrawable.getIntrinsicWidth(),mSelectedDrawable.getIntrinsicWidth());
宽度 = mCellWidth * mRow + (mRow + 1) * DEFAULT_PADDING;
}
返回宽度;
}

好了,我们都知道了视图的大小和点的大小,接下来我们需要根据我们传入的行数和列数来添加我们的点:

@覆盖
protected void onSizeChanged(int w,int h,int oldw,int oldh) {
super.onSizeChanged(w,h,oldw,oldh);
if (!isInitialed && getChildCount() == 0) {
已初始化 = true;
点 = new ArrayList<>();
addChildViews();
}
}

首先在onSizeChanged方法中添加我们的点(即子视图),因为onSizeChanged会被调用很多次,然后为了避免重复添加子视图,我们做了一个判断。第一次和child=0去添加子视图:

私人无效addChildViews(){
for (int i = 0; i < mRow; i++) {
for (int j = 0; j < mColumn; j++) {
GesturePoint 点 = new GesturePoint();
ImageView 图像 = new ImageView(getContext());
point.setImageView(图像);
int left = (int) ((j + 1) * DEFAULT_PADDING + j * mCellWidth);int top = (int) ((i + 1) * DEFAULT_PADDING + i * mCellHeight);
int 右 = (int) (左 + mCellWidth);
int 底部 = (int) (顶部 + mCellHeight);
point.setLeftX(左);
point.setRightX(右);
point.setTopY(顶部);
point.setBottomY(底部);
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)));
点.setPointX(i);
点.setPointY(j);
this.addView(image,(int) mCellWidth,(int) mCellHeight);
点.add(点);
}
}
}

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

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

所有的点信息都存储在一个名为points.add(point);的集合中。该集合存储 GesturePoint 对象:

公共类手势点{
//该点的左边距值
私有 int leftX;
//点的最高值
私有 int topY;
//点的右边距值
私有 int rightX;
私有 int 底部Y;
//点x轴的中值
私人int中心X;
私人国际中心Y;
//该点对应的行值
私有 int pointX;
//该点对应的列值
私有 int 点Y;
//点击对应的imageview
私有ImageView imageView;
//当前点状态:选中、未选中、错误
私有 PointState 状态;
//当前点对应的密码值
私有整数;
//绘制未选择的点
私人可绘制正常可绘制;
私人可绘制错误可绘制;
私有Drawable selectedDrawable;
}

现在我们已经添加了很多点,我们要做的就是按照行和列来排列我们的点的位置(重写onLayout方法来放置子视图(点)的位置):

@覆盖
protected void onLayout(布尔值改变,int l,int t,int r,int b) {
if (points!= null && points.size() > 0) {
for (GesturePoint 点: 点) {
点.layout();
}
}
}
公共无效布局(){
if (this.imageView != null) {
this.imageView.layout(leftX,topY,rightX,bottomY);
}
}

然后我们添加视图并运行代码:

<框架布局
安卓:layout_width =“match_parent”
安卓:layout_height =“0dp”
安卓:layout_weight =“1”
机器人:layout_margintop =“10dp”
>


渲染:

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

1。我们需要根据手指的位置检查该点是否在某个点内,并判断该点是否被选中。如果未选中,则将其状态更改为选中状态。

2。每次我们的手指连接两个点的时候,我们都需要判断是否有一个点在这两个点的中间。例如:如果我们的手指连接了(0, 0)和(2, 2)两个点,那么我们需要判断中间是否有一个点(1, 1)?那么需要将线段(0, 0) ~ (1, 1)和线段(1, 1) ~ (2, 2)保存到集合中,然后下次绘制线段时,起点将是 (2, 2) 点。

3。如果某个点没有被选中,我们会将这个点对应的num值(密码)存储到集合中。

4。当ACTION_UP事件执行时(即手指抬起时),回调方法将存储的集合数据传递给调用者。

@覆盖
公共布尔onTouchEvent(MotionEvent事件){
//是否允许用户绘图
if (!isDrawEnable) return super.onTouchEvent(event);
//设置画笔颜色为绘图颜色
linePaint.setColor(描边颜色);
int action = event.getAction();
//当手指按下时
if (MotionEvent.ACTION_DOWN == 动作) {
//清空画板
更改状态(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 == 动作) {
//、清空画板,然后绘制之前存储的线段
清除屏幕和绘制线();
//获取当前移动位置是否在某个点GesturePoint 点 = getPointByPosition((int) event.getX(),(int) event.getY());
//如果不在point范围内且currpoint也为空(手指移到画板外),则直接返回
if (point == null && currPoint == null) {
返回 super.onTouchEvent(事件);
} 别的 {
//当按下时该点为空,然后手指移动到某个点,将该点赋值给currpoint
if (currPoint == null) {
currPoint = 点;
//修改该点的状态
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);
} 别的 {
//修改该点的状态为选中
point.setState(PointState.POINT_STATE_SELECTED);
//将currpoint与当前点连接
lineCanvas.drawLine(currPoint.getCenterX(),point.getCenterX(),point.getCenterY(),linePaint);
//判断两点之间是否有点
List> BetweenPoints = getBetweenPoints(currPoint,point);//如果有点,则将中间点对应的线段存储在集合total中
if (afterPoints != null && BetweenPoints.size() > 0) {
pointPairs.addAll(BetweenPoints);
currPoint = 点;
pwds.add(point.getNum());
} 别的 {
pointPairs.add(new Pair(currPoint,point));
pwds.add(point.getNum());
currPoint = 点;
}
}
无效();
} else if (MotionEvent.ACTION_UP == 动作) {
//手指抬起时调用
if (gesturePwdCallBack != null) {
List datas=new ArrayList<>(pwds.size());
datas.addAll(pwds);
gesturePwdCallBack.callBack(数据);
}
}
返回真;
}

重点讲解一下getBetweenPoints方法(免责声明:我数学不好,用过好的方法的童鞋记得在评论里告诉我,谢谢~~我觉得我的方法很笨,呵呵~ !!):

私有列表> getBetweenPoints(GesturePoint currPoint,GesturePoint point) {
//定义一个集合来保存传入的点
Listpoints1 = new ArrayList<>();
点1.add(currPoint);
点1.add(点);
//对两点进行排序
Collections.sort(points1,new Comparator() {
@覆盖
公共 int 比较(GesturePoint o1,GesturePoint o2){
返回 o1.getNum() - o2.getNum();
}
});GesturePoint maxPoint = points1.get(1);
GesturePoint minPoint = points1.get(0);
点1.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> 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://www.webguidecorpuschristi.com/913453448/GestureContentView