Android进阶之自定义View实战(二)九宫格手势解锁实现
来源:互联网 发布:优化游戏软件 编辑:程序博客网 时间:2024/04/25 23:58
一.引言
在上篇博客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;//圆心x float mCenterPointY;//圆心y float 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=h private 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;//滑动过程中的折线和指引线paint private 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); } @Override protected 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(TypedValue.COMPLEX_UNIT_DIP, 300, r.getDisplayMetrics()); } if(hMode == MeasureSpec.AT_MOST){ resultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_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 block Block 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 */ @Override protected 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 */ @Override public 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); //探测未选中的block if(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){//手指第一次选中block mPath.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() { @Override public void onBlockHitted(int index) { Log.e("GestureLockView",index+""); } @Override public void onGestureLockSuccess(String password) { Log.e("GestureLockView",password); } @Override public void onGestureLockFail() { Log.e("GestureLockView","erro"); } });
希望大家通过这篇博客的分享,能在自定义View过程中对基本的触摸事件的处理如鱼得水!如有问题和更好的实现方案,希望大家指正。
- Android进阶之自定义View实战(二)九宫格手势解锁实现
- android 自定义view实现九宫格手势解锁
- android自定义view之九宫格解锁
- android自定义view之九宫格解锁
- 自定义view之九宫格手势解锁空间
- 自定义View----Android九宫格手势密码解锁
- 自定义View----Android九宫格手势密码解锁
- 自定义九宫格手势解锁
- 九点(九宫格)式手势解锁自定义view
- 九点(九宫格)式手势解锁自定义view
- Android九宫格手势解锁
- Android手势解锁, 九宫格解锁
- 【Android】自定义控件实现九宫格解锁
- Android自定义控件实现九宫格解锁
- android自定义手势解锁View
- 自定义View九宫格手势
- 自定义View--九宫格手势
- Android之九宫格解锁的实现
- 安卓完整开发环境配置
- 求二叉树中两个节点最远的距离
- 平台网格部件删除选择行
- ViewPage事件冲突处理
- tensorflow安装技巧
- Android进阶之自定义View实战(二)九宫格手势解锁实现
- 6.2 构造核
- TCP协议中的三次握手四次挥手问题
- 超时设置
- 《Spring实战》学习笔记-第八章:使用Spring Web Flow
- 1、junit学习之junit的基本介绍
- MongoDB配置为开机启动服务
- 0621 每日小记
- solrcloud简单介绍