Android自定义多宫格解锁控件
在此之前,一直在想九宫格的实现方法,经过一个上午的初步研究终于完成了一个简单的N*N的宫格解锁组件,代码略显粗糙,仅仅做到简单的实现,界面等后期在做优化,纯粹是学习的目的,在算法上有点缺陷,如果有错误或者更好的方法,欢迎提出,相互学习。先来看一下预览图
九宫格效果展示
N=3 手指抬起
N=4 手指没有抬起
其他的废话不多说了,直接开始吧.....
实现步骤
- 设置声明属性attrs.xml文件
- 创建SeniorPoint.java文件
- 创建View并重写其中的几个重要方法
- 设置触摸事件,并进行数据处理
- 设置回调函数,在Activity里面调用
设置声明属性
很简单的xml内容,在res文件夹里面新建文件attrs.xml,将下面的内容写入即可。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Lock">
<!--圆的半径-->
<attr name="circleRadius" format="dimension"></attr>
<!--圆的颜色-->
<attr name="circleColor" format="color"></attr>
<!--圆的线宽-->
<attr name="circlrWidth" format="dimension"></attr>
<!--线的宽度-->
<attr name="LineWidth" format="dimension"></attr>
<!--线的颜色-->
<attr name="LineColor" format="color"></attr>
<!--松开手之后线的颜色-->
<attr name="LineColorAfterLeave" format="color"></attr>
<!--圆圈的个数-->
<attr name="circlrNumber" format="integer"></attr>
</declare-styleable>
</resources>
创建SeniorPoint.java文件
SeniorPoint.java是一个Bean,里面保存着以圆心点的参考信息,代码如下:
package cn.example.tao.newview;
import android.graphics.Point;
/**
* Created by Tao on 2017/2/3.
*/
public class SeniorPoint extends Point {
private boolean isSelect=false;
public SeniorPoint(int x, int y, boolean isSelect) {
super(x, y);
this.isSelect = isSelect;
}
public boolean isSelect() {
return isSelect;
}
public void setSelect(boolean select) {
isSelect = select;
}
}
创建View并重写其中的几个重要方法
至于怎么自定义View这里不在过多的赘述,可以看一下我的文章,里面写了怎么自定义一个齿轮的View http://www.jianshu.com/p/104a9d7eeefd ,创建java文件Lock.java,继承View组件
获取在Activity布局中的设置属性值
在Activity的布局中的设置如下,具体每个属性的意义,请结合attrs.xml文件分析:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/sky"
>
<cn.example.tao.newview.widget.Lock
android:id="@+id/lock"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
tools:circleColor="#EAEAEA"
tools:circlrWidth="2dp"
tools:circleRadius="30dp"
tools:LineColor="#EAEAEA"
tools:LineWidth="3dp"
tools:LineColorAfterLeave="#77E6D8"
tools:circlrNumber="3"
/>
</LinearLayout>
首先获得我们在xml文件中设置的属性值,代码如下:
public Lock(Context context, AttributeSet attrs) throws Exception {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Lock);
//绘制圆的半径,默认值30dp
circleRadius = typedArray.getDimension(R.styleable.Lock_circleRadius, dp2px(30));
//绘制圆形的颜色,默认白色
circleColor = typedArray.getColor(R.styleable.Lock_circleColor, Color.WHITE);
//绘制圆形的宽度,默认3dp
circleWidth = typedArray.getDimension(R.styleable.Lock_circlrWidth, dp2px(3));
//折线的颜色
lineColor = typedArray.getColor(R.styleable.Lock_LineColor, Color.GRAY);
//折线的宽度
lineWidth = typedArray.getDimension(R.styleable.Lock_LineWidth, dp2px(1));
//连线完成后的线的颜色
lineColorAfterLeaver = typedArray.getColor(R.styleable.Lock_LineColorAfterLeave, Color.argb(255, 92, 186, 167));
//每行圆的数目,有事N*N,所以也是每列的数目,当然也可以根据次设置行数和列数不同的样式
circlrNumber=typedArray.getInt(R.styleable.Lock_circlrNumber,3);
typedArray.recycle();
//设置圆的数量为0或者负数的时候异常抛出
if (circlrNumber<1)
throw new Exception("圆的数量不能为0或负数");
//用字符串的形式保存点的位置,比如01代表第0行1列,当然可以在回调函数根据自己的需要设计
password=new StringBuffer();
mPaint = new Paint();
mPaint.setStrokeWidth(dp2px(5));
//初始化保存圆心位置的二维数组
location = new SeniorPoint[circlrNumber][circlrNumber];
}
private float dp2px(int i) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, i, getResources().getDisplayMetrics());
}
重写测量方法
在此直接写代码,不解释了
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getValueByComplete(widthMeasureSpec);
int height = getValueByComplete(heightMeasureSpec);
setMeasuredDimension(width, width);
}
public int getValueByComplete(int value) {
int size = MeasureSpec.getSize(value);
int mode = MeasureSpec.getMode(value);
int resultValue = 0;
if (mode == MeasureSpec.EXACTLY) {
resultValue = size;
} else {
resultValue = (int) mPaint.descent();
if (mode == MeasureSpec.AT_MOST)
resultValue = size;
}
return resultValue;
}
重写绘制方法
这里体现是对界面的绘制,主要是绘制圆和线,具体解释参考注释,在看一下这个懒到家的模型图,以N=3为参数画的,主要是注意一些点的设置
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//设置绘制圆形的时候,圆心的移动步长
int setp_x = getWidth() / circlrNumber;
int setp_y = getHeight() / circlrNumber;
//设置第一个圆的位置,后面的圆形的绘制都是相对于第一各院的圆心的位置进行移动,移动的单位也就是setp_x和setp_y
int mPaint_x = getWidth() / (2*circlrNumber), mPaint_y = getHeight() / (2*circlrNumber);
//设置绘制圆形的画笔信息
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(circleWidth);
mPaint.setColor(circleColor);
//循环,开始绘制圆形
for (int i = 0; i < circlrNumber; i++)
for (int j = 0; j < circlrNumber; j++) {
//此处开始绘保存圆心位置信息,设置为没有选中
if (location[i][j] == null)
location[i][j] = new SeniorPoint(mPaint_x + j * setp_x, mPaint_y + i * setp_y, false);
//开始绘制圆形,圆心坐标(mPaint_x + i * setp_x, mPaint_y + j * setp_y)
canvas.drawCircle(mPaint_x + i * setp_x, mPaint_y + j * setp_y, circleRadius, mPaint);
}
//使用arrayList保存被选中的点的信息
if (arrayList != null && arrayList.size() > 0) {
//如果存在被选中的点,则开始进行连线操作
//重新设置画笔的参数信息
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(lineWidth);
//如果现在的点的位置为(0,0)那么说明,手已经抬起来了,将这显得颜色更改为设置颜色,否则的话使用另外的颜色
if (nowPoint != null && nowPoint.x == 0 && nowPoint.y == 0)
mPaint.setColor(lineColorAfterLeaver);
else mPaint.setColor(lineColor);
//进行折线的绘制工作
for (int i = 0; i < arrayList.size(); i++) {
canvas.drawPoint(arrayList.get(i).x, arrayList.get(i).y, mPaint);
if (i != 0) {
canvas.drawLine(arrayList.get(i - 1).x, arrayList.get(i - 1).y, arrayList.get(i).x, arrayList.get(i).y, mPaint);
}
}
//如果手没有抬起,继续跟随手的位置来移动
if ((nowPoint != null && nowPoint.x != 0 && nowPoint.y != 0)) {
canvas.drawLine(arrayList.get(arrayList.size() - 1).x, arrayList.get(arrayList.size() - 1).y, nowPoint.x, nowPoint.y, mPaint);
}
}
}
设置触摸事件,并进行数据处理
@Override
public boolean onTouchEvent(MotionEvent event) {
// return super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//每次重新按下之前,要清除arratList中的列表保存的信息
arrayList.clear();
case MotionEvent.ACTION_MOVE:
//判断当前的手指的位置有没有在院内,如果在院内返回这个圆的的圆心并设置改圆为选中状态,否则返回null
SeniorPoint select = checkLocation(event.getX(), event.getY());
//配置点前手指的位置
if (nowPoint == null)
nowPoint = new SeniorPoint((int) event.getX(), (int) event.getY(), false);
else nowPoint.set((int) event.getX(), (int) event.getY());
if (select != null)
select.setSelect(true);
//重绘
invalidate();
break;
case MotionEvent.ACTION_UP:
//手指从屏幕离开后,将当前点的坐标设置为(0,0)
nowPoint.set(0, 0);
//离开后,读取已经选中的位置信息,返回给回调函数
//这里仅仅返回来的坐标点的位置,需要处理下才行
password.delete(0,password.length());
for (int i = 0; i < arrayList.size(); i++) {
password.append("第"+i+"个点的坐标 X:" + arrayList.get(i).x + " Y:" + arrayList.get(i).y + "\n");
}
if (onFinsh != null)
onFinsh.leaver(password.toString());
//重绘
invalidate();
break;
}
return super.onTouchEvent(event);
}
public SeniorPoint checkLocation(float x, float y) {
//此处循环检测九个点的位置,此处代码使用算法优化,没有必要循环判断位置
//后面有时间会专门写一个文章来分析下,追求更快的方法
double radio = dp2px(30);
for (int i = 0; i < circlrNumber; i++)
for (int j = 0; j < circlrNumber; j++) {
double l = getLong(x, y, location[i][j]);
if (l <= radio) {
//如果已选中的列表的长度为0 或者长度不是0,但是也不能和arryList表中上一个值完全一样,这样才能添加
if (arrayList.size() ==0 || (arrayList.size()>=1 && (location[i][j].x != arrayList.get(arrayList.size() - 1).x || location[i][j].y != arrayList.get(arrayList.size() - 1).y)))
arrayList.add(location[i][j]);
return location[i][j];
}
}
return null;
}
public double getLong(float x, float y, SeniorPoint point) {
//返回点前手指的点到圆形的位置
double s = Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2);
return Math.sqrt(s);
}
设置回调函数,在Activity里面调用
手抬起的时候,应该将选择的结果返回给Activity,在Activity中检查是否解锁成功,然后进行相应的处理.
首先定义接口,并在Lock.java文件中定义
private OnFinsh onFinsh;
//set方法
public void setOnFinsh(OnFinsh onFinsh) {
this.onFinsh = onFinsh;
}
//当手指抬起的时候,调用其leaver()方法,将结果回调到Activity中
//在上面的OnTouch事件中调用
if (onFinsh != null)
onFinsh.leaver(password.toString());
public interface OnFinsh {
void leaver(String password);
}
在Activity中这样使用
package cn.example.tao.newview;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import cn.example.tao.newview.widget.Lock;
public class MainActivity extends FragmentActivity {
//声明变量
private Lock lock;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
//绑定变量,并设置回调函数
lock= (Lock) findViewById(R.id.lock);
lock.setOnFinsh(new Lock.OnFinsh() {
@Override
public void leaver(String password) {
Log.e("PassWord",password);
}
});
}
}
打印的结果如下:
N=4的选中10个点
后记
当然也可以设置一下属性:
- 是否可见,设置为布尔型数据,如果true则绘制直线,否则不绘制直线。
- 增加选中一个点之后即调用的回调函数
- 设置类型为填充圆或者点或者图片
......
目前还存在的问题:
- 当手指处于某一点的时候,判断这个位置是不是在某一圆内,这里为了简单,使用了循环判断的方法,但是显然这种效率是很慢的,所以我想了下面的过程,不知是否合适:
1、将view界面想象分割成N*N的界面
2、首先大致判断手指的位置是不是在某个方格内,如果在,那么找到这个方格内的那个圆
3、通过一些逻辑计算得到这个圆的圆心位置
本博客内容一致同步到本人的博客站点:http://www.zhoutaotao.xyz 欢迎访问留言交流
Android自定义多宫格解锁控件的更多相关文章
- 【Android - 自定义View】之自定义九宫格手势解锁控件
首先来介绍一下这个自定义View: (1)这个自定义View的名称叫做 LockView ,继承自View类: (2)这个自定义View实现了应用中常见的九宫格手势解锁功能,可以用于保证应用安全: ( ...
- Android 自定义View之自绘控件
首先要提前声明一下,我对于自定义View的理解并不是很深,最近啃了几天guolin博主写的关于自定义View的博客,讲的非常棒,只不过涉及到源码和底层的一些东西,我自己就懵逼了,目前只是会了关于自定义 ...
- android 九宫格(16宫格)控件
public class NineRectView extends ViewGroup { private Context ctx; private int wSize,hSize,row,colum ...
- Android自定义drawable(Shape)详解
在Android开发过程中,经常需要改变控件的默认样式, 那么通常会使用多个图片来解决.不过这种方式可能需要多个图片,比如一个按钮,需要点击时的式样图片,默认的式样图片. 这样就容易使apk变大. 那 ...
- Android 自定义简易的方向盘操作控件
最近在做一款交互性较为复杂的APP,需要开发一个方向操作控件.最终用自定义控件做了一个简单的版本. 这里我准备了两张素材图,作为方向盘被点击和没被点击的背景图.下面看看自定义的Wheel类 publi ...
- WPF自定义控件之图形解锁控件 ScreenUnLock
ScreenUnLock 与智能手机上的图案解锁功能一样.通过绘制图形达到解锁或记忆图形的目的. 本人突发奇想,把手机上的图形解锁功能移植到WPF中.也应用到了公司的项目中. 在创建ScreenUnL ...
- ANDROID L——Material Design详解(UI控件)
转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...
- android - 自定义(组合)控件 + 自定义控件外观
转载:http://www.cnblogs.com/bill-joy/archive/2012/04/26/2471831.html android - 自定义(组合)控件 + 自定义控件外观 A ...
- Android自定义View(RollWeekView-炫酷的星期日期选择控件)
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义Cus ...
随机推荐
- Windows下安装及使用NVM
Windows下安装及使用NVM 所谓nvm就是一个可以让你在同一台机器上安装和切换不同版本node的工具.这里是一篇安装及使用教程. 第一步:下载nvm 可以到github上下载最新版本https: ...
- 1.Git起步-Git的三种状态以及三种工作区域、CVCS与DVCS的区别、Git基本工作流程
1.Git基础 版本控制系统是一种用于记录一个或多个文件内容变化,以便将来查阅恢复特定版本修订情况的系统. Git是一种分布式版本控制系统(Distributed Version Control Sy ...
- Linux 权限位详解
1. Linux 权限位 对于权限,有点绕,因为文件的权限和目录的权限是有一些区别的. 在Linux中,有5种权限,分别是,r.w.x.s.t. 可读权限:r 可写权限:w 可执行权限:x Setui ...
- 【ASP.NET MVC系列】浅谈ASP.NET MVC 视图
ASP.NET MVC系列文章 [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作 ...
- MySQL系列详解七:MySQL双主架构演示-技术流ken
前言 在企业中,数据库高可用一直是企业的重中之重,中小企业很多都是使用mysql主从方案,一主多从,读写分离等,但是单主存在单点故障,从库切换成主库需要作改动.因此,如果是双主或者多主,就会增加mys ...
- xmanager 连接centos 7桌面
1.前言 工作中服务器有时候需要图形处理一些事物,那么这个时候就需要远程连接方式,XDMCP,VNC,RDP,我今天介绍一下xdmp怎么使用与配置(x display manager control ...
- linux内核源码目录结构分析
原文地址 /arch.arch是architecture的缩写.arch目录下是好多个不同架构的CPU的子目录,譬如arm这种cpu的所有文件都在arch/arm目录下,X86的CPU的所有文件都在a ...
- JS_object添加变量属性_动态属性
总结,给对象动态添加变量属性的方法如下: obj[变量]=变量值; 备注: obj.属性=属性值 ; obj={属性:属性值}; 这两种方式添加的属性都不能使用变量作为属性. 犯过的错误: var t ...
- async,await,Task 的一些用法
async,await,Task 的一些用法 private void Form1_Load(object sender, EventArgs e) { Display(); } public asy ...
- mybatis_08 mybatis与hibernate的区别
这个问题常常被面试官当做面试题 mybatis与hibernate喜忧参半,各有特点 Mybatis技术特点: 好处: 1. 通过直接编写SQL语句,可以直接对SQL进行性能的优化: 2. 学习门 ...