我们继续上一篇博客的内容吧。在上一节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) { //定义一个集合来保存传入的点 List points1 = 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