onDraw这个方法在自定义中尤其重要,我们可以measure之后通过Canvas进行绘制,九宫格解锁这个View现在已经被人脸跟指纹给替代了,但是做起来还是有点东西的。

下面就是做这个View的思路:

九个格子的布局

格子之间的连线

格子之间的连线所要考虑的问题

一 . 格子布局

之前写过onlayout来进行View的排放,那要继承于ViewGroup,这次我继承之View来实现。在onDraw中进行位置的摆放。整个的思路就是整出一个正方形,然后均与摆放9个格子的位置。本次摆放的方法是摆放一个5*5的矩形,即两个圆之间的间隔也是一个圆,这样方便了后期的计算。首先是计算每个球的圆心:

radius = (measureWidth - 100) / 5;

innerPadding = 50;

radius = radius / 2;

int centerX = innerPadding + radius, centerY = innerPadding + radius;

for (int i = 0; i < 9; i++) {

pointList.add(new Point(centerX, centerY));

centerX += 4 * radius;

if (centerX > (measureWidth - 100)) {

centerX = innerPadding + radius;

centerY += 4 * radius;

}

}

这里的操作就跟之前的Android onLayout()摆放位置是一个道理,然后在onDraw()中:

for (Point mPoint : pointList) {

if (mPoint.isSelected) {

paint.setColor(Color.BLUE);

} else {

paint.setColor(Color.WHITE);

}

canvas.drawCircle(mPoint.x, mPoint.y, radius, paint);

}

这里默认的是白色的,当被选中的时候换成蓝色的。很简单我们的九宫格就画出来了,那么看看如何进行交互的。

二. 格子之间的连线

第一反应是用canvas.drawLine()来实现,实际操作并没有什么用,因为当画到一个点的时候,出发点就是当前的这个点了,之前的线段用drawLine()并不好用绘制,这里要用Path来实现,通过LineTo到某个点然后drawPath():

canvas.drawPath(path, linePaint);

canvas.drawLine(startX, startY, endX, endY, linePaint);

onDraw中的剩余代码,第一句表示的是画线段,记录已经画出的线段,第二个则是记录画出的点,这里固定(startX,startY),改变 (endX, endY)来实现伸缩的线段。

我们来看一下onTouch方法。onTouch有Down, Move,Up,这三种是比较常用的方法,这个后面再进行拓展。

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

break;

case MotionEvent.ACTION_MOVE:

break;

case MotionEvent.ACTION_UP:

break;

}

return true;

}

看一下Down里面的方法:

setAllunSelected();

path.reset();

int i = getPoint(x, y);

if (DEFAULT_NUM == i) {

invalidate();

return false;

} else {

//说明点在点子上了

selectList.add(i);

pointList.get(i).setSelected(true);

path.moveTo(pointList.get(i).getX(), pointList.get(i).getY());

startX = pointList.get(i).getX();

startY = pointList.get(i).getY();

}

首先把之前的都清除掉用setAllunSelected()把所有点的状态置成白色的状态,再把path重置一下,int i = getPoint(x, y); 来计算一下当前按下的这个点是不是在某个九宫格中的某个圆里面,这时候把当前的点放进selectList集合里面并且把这个点的状态置成被选中的状态,这就成了我们可以记录的画出密码的线路,也就是我们的密码。因为是第一次按下去,所以把当前的startX,startY换成当前选中的点,做为我们的出发点。

后面紧跟onMove代码:

endX = x;

endY = y;

int j = getPoint(x, y);

if (DEFAULT_NUM != j) {

if(selectList.contains(j)){

invalidate();

break;

}

int missNum = getPointNum(j);

if(missNum!=DEFAULT_NUM && !selectList.contains(j) ){

selectList.add(missNum);

pointList.get(missNum).setSelected(true);

}

selectList.add(j);

pointList.get(j).setSelected(true);

path.lineTo(pointList.get(j).getX(), pointList.get(j).getY());

startX = pointList.get(j).getX();

startY = pointList.get(j).getY();

}

invalidate();

这里我们做的就是移动到当前的点我们就改成endX,endY,还是通过getPoint这个方法来判断是不是按在某个点里面:

private int getPoint(float x, float y) {

for (int i = 0; i < pointList.size(); i++) {

int j = getPosition(x, y, i);

if (DEFAULT_NUM != j) {

return j;

}

}

return DEFAULT_NUM;

}

private int getPosition(float x, float y, int position) {

Point point = pointList.get(position);

if (Math.hypot(Math.abs(x - point.x), Math.abs(y - point.y)) < radius) {

return position;

}

return DEFAULT_NUM;

}

三.连线要考虑的问题

getPoint()方法就是记录就是计算当前点距离是不是在某个我们画的圆里面,分别到9个圆心的距离就可以判断是不是在当前这个圆里面,然后可以确定是点击的第几个圆。后面我们进行了一次判断假如我们selectList中已经有了某个点,当我们再滑动到他的时候不进行任何操作。后面加了一个方法getMissNum这个方法是后面思考的时候加上的主要的实现就是看图1跟图2的区别:

图1.png

图2.png

实际区别就是能不能主动吸附到中间这个点的问题代码:

private int getPointNum(int position){

if(selectList.size()<1)

{

return DEFAULT_NUM;

}

int size =selectList.size();

int i = selectList.get(size-1);

Point p = pointList.get(position);

Point point = pointList.get(i);

//判断是不是由上往下,从左往右

boolean b = false ;

if(position>i){

b = true;

}

if(Math.abs(p.getX()-point.getX())!=4*radius ||Math.abs(p.getX()-point.getX())!=4*radius){

//说明不是相邻的两个点

if(Math.abs(p.getX()-point.getX()) == 0 ||Math.abs(p.getY()-point.getY())==0 ){

//说明一排或者一列中间有个没点到的

if(Math.abs(p.getY()-point.getY()) == 0) {

if(b){

return i+1;

}else{

return i-1;

}

}else {

if(b){

return i+3;

}else{

return i-3;

}

}

}else if(Math.abs(p.getX()-point.getX())==8*radius && Math.abs(p.getY()-point.getY())==8*radius){

//这里是对角线

return CENTER_NUM;

}

}

return DEFAULT_NUM;

}

本来想通过Path转换来看看某个点在不在这个Path区域中,后来实践之后发现并不能做的出来,也把代码贴出来:

private boolean pointInPath(Path path, Point point) {

RectF bounds = new RectF();

path.computeBounds(bounds, true);

Region region = new Region();

region.setPath(path, new Region((int) bounds.left, (int) bounds.top,

(int) bounds.right, (int) bounds.bottom));

return region.contains((int)point.x, (int)point.y);

}

最后就是将绘制好的密码扔出去,可以的话再添加个回掉把密码组给扔出去。

完整代码:

public class LockView extends View {

final int DEFAULT_NUM = -1;

final int CENTER_NUM = 4;

String TAG = "LockView";

int measureWidth;

private int innerPadding;

private int radius;

List pointList;

Paint paint;

Paint linePaint;

float startX, startY;

float endX, endY;

Path path;

ArrayList selectList;

boolean dismiss = false;

boolean isMove = false;

public LockView(Context context) {

this(context, null);

}

public LockView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public LockView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

pointList = new ArrayList<>();

init();

}

private void init() {

paint = new Paint();

paint.setStrokeWidth(2);

paint.setStyle(Paint.Style.FILL);

paint.setAntiAlias(true);

paint.setColor(Color.WHITE);

linePaint = new Paint();

linePaint.setStrokeWidth(20);

linePaint.setStyle(Paint.Style.STROKE);

linePaint.setAntiAlias(true);

linePaint.setColor(Color.WHITE);

linePaint.setStrokeJoin(Paint.Join.ROUND);

//线条结束处绘制一个半圆

linePaint.setStrokeCap(Paint.Cap.ROUND);

selectList = new ArrayList<>();

path = new Path();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int height = MeasureSpec.getSize(heightMeasureSpec);

int width = MeasureSpec.getSize(widthMeasureSpec);

int size = Math.min(width, height);

setMeasuredDimension(size, size);

measureWidth = getMeasuredWidth();

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

Log.d(TAG, "onSizeChanged: " + measureWidth);

radius = (measureWidth - 100) / 5;

innerPadding = 50;

radius = radius / 2;

int centerX = innerPadding + radius, centerY = innerPadding + radius;

for (int i = 0; i < 9; i++) {

pointList.add(new Point(centerX, centerY));

centerX += 4 * radius;

if (centerX > (measureWidth - 100)) {

centerX = innerPadding + radius;

centerY += 4 * radius;

}

}

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

if(dismiss){

setAllunSelected();

path.reset();

}

for (Point mPoint : pointList) {

if (mPoint.isSelected) {

paint.setColor(Color.BLUE);

} else {

paint.setColor(Color.WHITE);

}

canvas.drawCircle(mPoint.x, mPoint.y, radius, paint);

}

canvas.drawPath(path, linePaint);

canvas.drawLine(startX, startY, endX, endY, linePaint);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();

float y = event.getY();

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

setAllunSelected();

dismiss = false;

path.reset();

int i = getPoint(x, y);

if (DEFAULT_NUM == i) {

invalidate();

return false;

} else {

//说明点在点子上了

selectList.add(i);

pointList.get(i).setSelected(true);

path.moveTo(pointList.get(i).getX(), pointList.get(i).getY());

startX = pointList.get(i).getX();

startY = pointList.get(i).getY();

}

break;

case MotionEvent.ACTION_MOVE:

endX = x;

endY = y;

int j = getPoint(x, y);

if (DEFAULT_NUM != j) {

if(selectList.contains(j)){

invalidate();

break;

}

int missNum = getPointNum(j);

if(missNum!=DEFAULT_NUM && !selectList.contains(j) ){

selectList.add(missNum);

pointList.get(missNum).setSelected(true);

}

selectList.add(j);

pointList.get(j).setSelected(true);

path.lineTo(pointList.get(j).getX(), pointList.get(j).getY());

startX = pointList.get(j).getX();

startY = pointList.get(j).getY();

}

invalidate();

break;

case MotionEvent.ACTION_UP:

endX = startX;

endY = startY;

dismiss = true ;

invalidate();

break;

default:

break;

}

return true;

}

private int getPoint(float x, float y) {

for (int i = 0; i < pointList.size(); i++) {

int j = getPosition(x, y, i);

if (DEFAULT_NUM != j) {

return j;

}

}

return DEFAULT_NUM;

}

private int getPosition(float x, float y, int position) {

Point point = pointList.get(position);

if (Math.hypot(Math.abs(x - point.x), Math.abs(y - point.y)) < radius) {

return position;

}

return DEFAULT_NUM;

}

class Point {

float x;

float y;

boolean isSelected;

public Point(float x, float y) {

this.x = x;

this.y = y;

}

public float getX() {

return x;

}

public void setX(float x) {

this.x = x;

}

public float getY() {

return y;

}

public void setY(float y) {

this.y = y;

}

public boolean isSelected() {

return isSelected;

}

public void setSelected(boolean selected) {

isSelected = selected;

}

}

private void setAllunSelected() {

for (int i = 0; i < pointList.size(); i++) {

pointList.get(i).setSelected(false);

}

selectList.clear();

}

private int getPointNum(int position){

if(selectList.size()<1)

{

return DEFAULT_NUM;

}

int size =selectList.size();

int i = selectList.get(size-1);

Point p = pointList.get(position);

Point point = pointList.get(i);

//判断是不是由上往下,从左往右

boolean b = false ;

if(position>i){

b = true;

}

if(Math.abs(p.getX()-point.getX())!=4*radius ||Math.abs(p.getX()-point.getX())!=4*radius){

//说明不是相邻的两个点

if(Math.abs(p.getX()-point.getX()) == 0 ||Math.abs(p.getY()-point.getY())==0 ){

//说明一排或者一列中间有个没点到的

if(Math.abs(p.getY()-point.getY()) == 0) {

if(b){

return i+1;

}else{

return i-1;

}

}else {

if(b){

return i+3;

}else{

return i-3;

}

}

}else if(Math.abs(p.getX()-point.getX())==8*radius && Math.abs(p.getY()-point.getY())==8*radius){

//这里是对角线

return CENTER_NUM;

}

}

return DEFAULT_NUM;

}

private boolean pointInPath(Path path, Point point) {

RectF bounds = new RectF();

path.computeBounds(bounds, true);

Region region = new Region();

region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom));

return region.contains((int)point.x, (int)point.y);

}

}

最后关于一些想使用的自定义属性自己可以直接定义一下,包括圆的样式什么的都是可以自己自定义搞一下的。这里就不写了。只是实现了一下这个简单的功能,对onDraw()方法中画图有一定认识。主要有画circle,画Path ,画Line的实战。

android九宫格布局解锁,Android onDraw()--九宫格解锁相关推荐

  1. android 长方形九宫格布局,房子九宫格布局,长方形房屋九宫格怎么画

    1,长方形房屋九宫格怎么画 也可直接点"搜索资料"搜索整个问题. 长方形 九宫格 房屋 搜索资料 本地图片 图片链接 提交回答 匿名 回答自动保存中为你推荐:特别推荐... 2,什 ...

  2. android渐变布局,关于Android的径向渐变高级编程的实现

    在最近的一系列文章,对midipad APP,有一个关于一个radialgradiant渲染每个padview利用的探讨,对审美的原因,这是一个软件层,而不是一个硬件层.在这个简短的系列中,我们首先看 ...

  3. android 线性布局位置,android – 如何在线性布局中更改视图的位置.

    所以我有一个已经填充了子项的线性布局.有没有办法改变其中一个孩子的位置? 如果有任何帮助的话,我正试图交换他们之间的观点. final LinearLayout parrent = (LinearLa ...

  4. android 获取布局textview,android – 获取TextView中文本的位置

    看看几个Paint方法: getTextBounds()和 measureText.我们可以使用它们来确定TextView中文本的偏移量.确定TextView中的偏移后,我们可以将其添加到TextVi ...

  5. android局部布局替换,Android 局部布局替换的实现方式

    最近再搞远程视频的功能,其中要实现加载视频.加载视频失败.加载成功的局部布局替换,查阅相关资料,找到一种投机取巧的方式. 首先分别写这三种效果的子布局,分别为 top_remotetreate.xml ...

  6. android圆角布局阴影,Android 布局阴影实现

    最近项目要求,ui有很多有关于阴影的设计要求,网上找了些实现方式,但都不是很理想.现在闲下来了,就寻思着自己写个阴影布局耍耍,以备后用.先说道说道我找到的几种阴影实现方式: 系统阴影 Andorid ...

  7. android ui布局适配,Android适配全面总结(一)----屏幕适配

    前言 Android适配是一个老生常谈的问题,很多程序员觉得很恶心,不愿意做适配,但是又不得不做.然后老板说,这位兄弟,做好了,今天晚饭给你加个鸡腿,然后程序员开始找各种资料,忙活起来了,最终在苦逼的 ...

  8. android滚动条布局横向,Android自定义ViewGroup实现可滚动的横向布局(2)

    这里直接代码: package com.example.libingyuan.horizontallistview.ScrollViewGroup; import android.content.Co ...

  9. android相对布局代码,Android基础_3 Activity相对布局(示例代码)

    相对布局要比前面讲的线性布局和表格布局要灵活一些,所以平常用得也是比较多的.相对布局控件的位置是与其周围控件的位置相关的,从名字可以看出来,这些位置都是相对的,确定出了其中一个控件的位置就可以确定另一 ...

  10. android 首页布局变换,Android XML布局与View之间的转换

    Android的布局方式有两种,一种是通过xml布局,一种是通过java代码布局,两种布局方式各有各的好处,当然也可以相互混合使用.很多人都习惯用xml布局,那xml布局是如何转换成view的呢?本文 ...

最新文章

  1. HADOOP都升级到2.5啦~~~
  2. java 8进制 前是 零,从零学java笔录-第6篇 进制之间转换
  3. 多线程编程(2): 线程的创建、启动、挂起和退出
  4. 消息通知调度服务器,mPaaS 服务端核心组件:消息推送 MPS 架构及流程设计
  5. maxon电机中文名_MAXON电机完全手册(全系列).pdf
  6. excel数据正在计算机,excel数据太多表格太卡-急!Excel数据量大,电脑卡死?
  7. MSCI 明晟D.J. Orr博士:中国量化投资不缺人才缺工具
  8. 全国省份、城市关联表 mysql(含城市名拼音)
  9. 突发!意外!华芯通公司将于4月30日关闭
  10. Fiddler抓包配置和使用(全网最详细教程)
  11. WPF ContextMenu的使用
  12. P4924 [1007]魔法少女小Scarlet
  13. 教育知识与能力简答题知识点
  14. 360N4救砖刷机 全程完整操作
  15. [美文欣赏] 春风沉醉的晚上
  16. http协议:http协议理解
  17. 人性26——让我的蠢猫通人性吧
  18. 无人驾驶-GPS地图采集使用说明
  19. React18报错警告:You are calling ReactDOMClient.createRoot() on a container that has already been passed
  20. 一些问题的解决,分析

热门文章

  1. 17个C语言字符串处理函数
  2. winfrom快递追踪小程序 实现实时推送快递状态到手机
  3. 虹软人脸识别初试(windows SDK,idea,USB摄像头)
  4. LabVIEW编程基础:事件结构框架编程
  5. DataV在浏览器放大缩小以及全屏前后不能自适应问题
  6. idea的maven项目中报错:前言中不允许有内容
  7. Seq2Seq模型PyTorch版本
  8. 【Axure高保真原型】音频上传和播放
  9. Module not found: Error: Can‘t resolve ‘./@/assets/image/folder.png‘
  10. [Codevs] 3287 货车运输