700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Android进阶之自定义View实战(二)九宫格手势解锁实现

Android进阶之自定义View实战(二)九宫格手势解锁实现

时间:2019-07-14 05:23:46

相关推荐

Android进阶之自定义View实战(二)九宫格手势解锁实现

一.引言

在上篇博客Android进阶之自定义View实战(一)仿iOS UISwitch控件实现中我们主要介绍了自定义View的最基本的实现方法。作为自定义View的入门篇,仅仅介绍了Canvas的基本使用方法,而对用户交互层面仅仅处理了单击事件接口,在实际的业务中,常常涉及到手势操作,本篇博客以九宫格手势解锁View为例,来说明自定义View如何根据需求处理用户的手势操作。虽然九宫格手势解锁自定义View网上资料有很多,实现原理大同小异,但这里我只是根据自己觉得最优的思路来实现它,目的是让更多的初学者能看清我的思想,更快的掌握它的套路。话不多说,先看效果图,本人纯种工科男,颜色大家看看就好~_~!(ps:as的录屏效果有一半屏幕是花的,所以上图片将就一下。。);

1.手指滑动状态

2.手指释放后,校验失败

3.手指释放后,校验成功

二.案例分析

根据上面的三张图,可以看出手势锁有下面几个要素:

1.九宫格阵列状态,每个格子有三种状态:空闲、击中、校验失败、校验成功,其中击中状态是在手指移动过程中产生,后面的校验状态是手指释放后产生。格子状态的改变都在View的触摸事件里处理。

2.九宫格的绘制元素,每个格子有半透明大圆、深色小圆、三角;在移动过程中的手指路径,包括两个:格子与格子之间的连线和路径的”尾巴”:动态探测线。

3.触摸事件所需要处理的逻辑主要有每个格子的状态处理和路径规划。

1>根据手指的位置,确定击中的节点,在down和move事件改变它的状态;

2>在move事件中,探测击中的节点,如果探测到则绘制连线,否则绘制探测线。

3>在up或者cancel事件中,取消探测线,并根据击中的节点校验密码,更新状态,计算三角的方向,重绘节点。

三.范例代码

1.通过第二节的分析,格子(Block)是这个View的基本构成单元,包含状态、位置、大小半径、三角等元素,触摸事件的处理都是真的Block的处理。Block代码:

package com.star.gesturelock;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;/*** 一个阵列的基本组成单元* Created by kakaixcm on 16/6/7.*/public class Block {float mCenterPointX;//圆心xfloat mCenterPointY;//圆心yfloat mBigRadius;//大圆半径float mLittleRadius;//小圆半径BlockSate mState = BlockSate.IDLE;//默认空闲int mId;//索引//空闲状态颜色int mIdleBigCircleColor = Color.parseColor("#110000ff");int mIdleLittleCircleColor = Color.parseColor("#0000ff");//选中状态颜色int mHittedBigCircleColor = Color.parseColor("#1100ff00");int mHittedLittleCircleColor = Color.parseColor("#00ff00");//密码通过的颜色int mSuccessBigCircleColor = Color.parseColor("#1100ff00");int mSuccessdLittleCircleColor = Color.parseColor("#00ff00");//密码错误时的颜色int mErroBigCircleColor = Color.parseColor("#11ff0000");int mErroLittleCircleColor = Color.parseColor("#ff0000");//三角Path mArrow = new Path();//三角指向角度,水平向右为0度,顺时针方向为正double mArrowAngle;public void setArrowAngle(double angle){mArrowAngle = angle;}public void drawArrow(Canvas canvas, Paint paint){//没有松手,则不画三角if(mState != BlockSate.SUCCESS && mState != BlockSate.ERRO){return;}float arrowLen = (mBigRadius - mLittleRadius)*0.5f;float arrowLeftX = mCenterPointX + mLittleRadius + (mBigRadius - mLittleRadius - arrowLen)/2;float arrowRightX = arrowLeftX + arrowLen;float topY = mCenterPointY - arrowLen;float bottomY = mCenterPointY + arrowLen;mArrow.moveTo(arrowRightX, mCenterPointY);mArrow.lineTo(arrowLeftX, topY);mArrow.lineTo(arrowLeftX, bottomY);mArrow.close();canvas.save();canvas.rotate((float) mArrowAngle, mCenterPointX, mCenterPointY);canvas.drawPath(mArrow, paint);canvas.restore();}public enum BlockSate {IDLE,//空闲HITTED,//手指触摸ERRO,//密码错误SUCCESS;//密码正确}}

说明:drawArrow实现根据指向下一节点的方向绘制三角,三角形采用Path实现,三角形的方向调整则通过canvas.rotate方法实现。每个节点的指引角度是在手势释放后,根据击中的节点列表来依次计算。

2.GestureLockView实现:

package com.star.gesturelock;import android.content.Context;import android.content.res.Resources;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.text.TextUtils;import android.util.AttributeSet;import android.util.Log;import android.util.TypedValue;import android.view.MotionEvent;import android.view.View;import java.util.ArrayList;import java.util.List;/*** Created by kakaxicm on 16/6/7.*/public class GestureLockView extends View {private float mSize;//w=hprivate final float MBIGRADIUSFRACTION = 40/300.0f;private final float MLITTLERADIUSFRACTION = 15/300.0f;private float mLittleRadius;//小圆半径private float mBigRadius;//大圆半径private List<Block> mBaseBlocks = new ArrayList<>();private List<Integer> mSelectedIds = new ArrayList<>();private Paint mBigCirclePaint;private Paint mSmallCirclePaint;private Paint mLinePaint;//滑动过程中的折线和指引线paintprivate Path mPath;//滑动过程中的折线private float mNodeLineX;//折线的节点位置private float mNodeLineY;private float mLineTmpX;//指引线的终点private float mLineTmpY;String mAnswer = "012543678";//预设密码/*** 手势锁回调*/public interface OnGestureLockListener{void onBlockHitted(int index);//block被触摸到void onGestureLockSuccess(String password);void onGestureLockFail();}private OnGestureLockListener mGestureLockListener;public void setmGestureLockListener(OnGestureLockListener listener){mGestureLockListener = listener;}public GestureLockView(Context context, AttributeSet attrs) {super(context, attrs);setBackgroundColor(Color.GRAY);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int wMode = MeasureSpec.getMode(widthMeasureSpec);int hMode = MeasureSpec.getMode(heightMeasureSpec);int wSize = MeasureSpec.getSize(widthMeasureSpec);int hSize = MeasureSpec.getSize(heightMeasureSpec);int resultWidth = wSize;int resultHeight = hSize;Resources r = Resources.getSystem();//lp = wrapcontent时 指定默认值if(wMode == MeasureSpec.AT_MOST){resultWidth = (int) TypedValue.applyDimension(PLEX_UNIT_DIP, 300, r.getDisplayMetrics());}if(hMode == MeasureSpec.AT_MOST){resultHeight = (int) TypedValue.applyDimension(PLEX_UNIT_DIP, 300, r.getDisplayMetrics());}int size = resultWidth>resultHeight?resultHeight:resultWidth;setMeasuredDimension(size, size);initParams();}/*** 绘制涉及参量的初始化操作*/private void initParams(){mSize = getMeasuredWidth();mBigRadius = MBIGRADIUSFRACTION*mSize;mLittleRadius = MLITTLERADIUSFRACTION*mSize;mBigCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mSmallCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mLinePaint.setStyle(Paint.Style.STROKE);mLinePaint.setStrokeCap(Paint.Cap.ROUND);mLinePaint.setStrokeJoin(Paint.Join.ROUND);mLinePaint.setStrokeWidth(mLittleRadius*2);mLinePaint.setColor(Color.parseColor("#4400ff00"));mPath = new Path();//blocks初始化if(mBaseBlocks.size() == 0){for(int i = 0; i < 3; i++){for(int j = 0; j < 3; j++){//构建3*3 blockBlock block = new Block();float centerX = mSize*(1+j*2)/6;float centerY = mSize*(1+i*2)/6;block.mCenterPointX = centerX;block.mCenterPointY = centerY;block.mBigRadius = mBigRadius;block.mLittleRadius = mLittleRadius;block.mId = i*3+j;mBaseBlocks.add(block);}}}}/*** 绘制blocks、折线、指引线* @param canvas*/@Overrideprotected void onDraw(Canvas canvas) {for(int i = 0; i < mBaseBlocks.size(); i++){Block block = mBaseBlocks.get(i);drawBlock(canvas, block);}//绘制折线和指引线canvas.drawPath(mPath, mLinePaint);if(mSelectedIds.size()>0){canvas.drawLine(mNodeLineX, mNodeLineY, mLineTmpX, mLineTmpY, mLinePaint);}}/*** 绘制基本单元* 1.大、小圆* 2.三角指示* @param canvas* @param block*/private void drawBlock(Canvas canvas, Block block){if(block.mState == Block.BlockSate.IDLE){mBigCirclePaint.setColor(block.mIdleBigCircleColor);mSmallCirclePaint.setColor(block.mIdleLittleCircleColor);}else if(block.mState == Block.BlockSate.HITTED){mBigCirclePaint.setColor(block.mHittedBigCircleColor);mSmallCirclePaint.setColor(block.mHittedLittleCircleColor);}else if(block.mState == Block.BlockSate.SUCCESS){mBigCirclePaint.setColor(block.mSuccessBigCircleColor);mSmallCirclePaint.setColor(block.mSuccessdLittleCircleColor);}else if(block.mState == Block.BlockSate.ERRO){mBigCirclePaint.setColor(block.mErroBigCircleColor);mSmallCirclePaint.setColor(block.mErroLittleCircleColor);}canvas.drawCircle(block.mCenterPointX,block.mCenterPointY, block.mBigRadius, mBigCirclePaint);canvas.drawCircle(block.mCenterPointX,block.mCenterPointY, block.mLittleRadius, mSmallCirclePaint);//画三角指示符if(mSelectedIds.size() > 0){if(block.mId != mSelectedIds.get(mSelectedIds.size()-1)){//最后一个不画三角block.drawArrow(canvas,mSmallCirclePaint);}}}/*** 核心代码,控制手势监听的逻辑* step1:ACTION_DOWN 做复位操作* setp2:ACTION_MOVE 监测手指滑到哪个block,同时更新block状态、指引线及折线* step3:ACTION_UP 校验密码、更新选中的block状态、设置选中的block三角角度* srep4:前三步都会更改绘制涉及的参数,需要重绘操作* @param event* @return*/@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_DOWN:reset();break;case MotionEvent.ACTION_MOVE:float x = event.getX();float y = event.getY();Block block = checkHitBlock(x, y);//探测未选中的blockif(block != null && !mSelectedIds.contains(block.mId)){//探测到if(mGestureLockListener != null){mGestureLockListener.onBlockHitted(block.mId);}//手指触摸到block,作以下处理://1.block状态处理//2.path的节点设置为block的中心//3.指引线的终点设为节点位置block.mState = Block.BlockSate.HITTED;mSelectedIds.add(block.mId);mNodeLineX = block.mCenterPointX;mNodeLineY = block.mCenterPointY;//折线变为block的圆心if(mSelectedIds.size() == 1){//手指第一次选中blockmPath.moveTo(mNodeLineX, mNodeLineY);}else{mPath.lineTo(mNodeLineX, mNodeLineY);}mLineTmpX = mNodeLineX;mLineTmpY = mNodeLineY;}else{//未探测到//手指未触摸到block,则只需要设置指引线终点即可mLineTmpX = x;mLineTmpY = y;}break;case MotionEvent.ACTION_UP://选中的block 改为error/success状态changeReleaseBlockState();//折线处理,终点回退到节点,实现取消指引线的效果mLineTmpX = mNodeLineX;mLineTmpY = mNodeLineY;//三角角度设置configBlockArrowAngles();break;default:break;}invalidate();return true;}/*** 手指松开时,根据选中的block,设置三角的角度*/private void configBlockArrowAngles(){for(int i = 0; i < mSelectedIds.size()-1; i++){int index = mSelectedIds.get(i);int nextIndex = mSelectedIds.get(i+1);Block curBlock = mBaseBlocks.get(index);Block nextBlock = mBaseBlocks.get(nextIndex);float offsetX = nextBlock.mCenterPointX - curBlock.mCenterPointX;float offsetY = nextBlock.mCenterPointY - curBlock.mCenterPointY;double angle = Math.toDegrees(Math.atan2(offsetY,offsetX));curBlock.setArrowAngle(angle);Log.e("ANGLES",angle+"");}}/*** 松手时,检测结果,修改选中的block状态*/private void changeReleaseBlockState(){StringBuilder sb = new StringBuilder();for(int i = 0;i < mSelectedIds.size();i++){sb.append(mSelectedIds.get(i));}boolean isSuccess = TextUtils.equals(mAnswer, sb.toString());if(mGestureLockListener != null){if(isSuccess){mGestureLockListener.onGestureLockSuccess(mAnswer);}else {mGestureLockListener.onGestureLockFail();}}//设置选中的block的状态for(int i = 0; i < mBaseBlocks.size(); i++){Block block = mBaseBlocks.get(i);if(mSelectedIds.contains(block.mId)){if(isSuccess){block.mState = Block.BlockSate.SUCCESS;}else{block.mState = Block.BlockSate.ERRO;}}}}/*** 复位所有block、path、指引线状态*/private void reset(){mPath.reset();mSelectedIds.clear();for(int i = 0; i < mBaseBlocks.size(); i++){Block block = mBaseBlocks.get(i);block.mState = Block.BlockSate.IDLE;block.mArrowAngle = 0;}mLineTmpX = 0;mLineTmpY = 0;mNodeLineX = 0;mNodeLineY = 0;}/*** 检测位置落在哪个block上* @param x* @param y* @return*/private Block checkHitBlock(float x, float y){for(int i = 0; i < mBaseBlocks.size(); i++){Block block = mBaseBlocks.get(i);float startX = block.mCenterPointX - block.mBigRadius;float endX = block.mCenterPointX + block.mBigRadius;float startY = block.mCenterPointY - block.mBigRadius;float endY = block.mCenterPointY + block.mBigRadius;if(x >= startX && x <=endX && y >= startY && y <= endY){return block;}}return null;}}

说明:

1>initParams方法初始化了各个画笔、连接点。核心是初始化3*3的Block阵列。

2>核心逻辑处理逻辑在onTouchEvent中:

ACTION_DOWN方法复位整个View的状态;

ACTION_MOVE做探测击中的节点,如果探测到,则更新连线节点、探测线的起点以及节点状态(Hitted),如果未探测到,则只需更新探测线的终点;

ACTION_UP校验密码并更新所有被选中的节点的状态和三角箭头方向、取消探测线绘制。上面的操作均改变了绘制涉及的参量,所以都唤起View重绘。

3> configBlockArrowAngles方法根据两个节点的位置计算三角箭头的方向;changeReleaseBlockState()主要做密码校验用;checkHitBlock做从手指位置到节点的探测;drawBlock主要实现不同状态下的节点和三角器绘制;onDraw方法调用每个节点的绘制和折线、探测线的绘制。

用例:

GestureLockView lockView = (GestureLockView) findViewById(R.id.gesturelock);lockView.setmGestureLockListener(new GestureLockView.OnGestureLockListener() {@Overridepublic void onBlockHitted(int index) {Log.e("GestureLockView",index+"");}@Overridepublic void onGestureLockSuccess(String password) {Log.e("GestureLockView",password);}@Overridepublic void onGestureLockFail() {Log.e("GestureLockView","erro");}});

希望大家通过这篇博客的分享,能在自定义View过程中对基本的触摸事件的处理如鱼得水!如有问题和更好的实现方案,希望大家指正。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。