两种事件处理方式 图案解锁demo
内容
-
事件处理
-
触摸事件
// 重写触摸回调事件onTouchEvent
1.创建一个xml界面
// 返回值表示这个事件是否已经被处理
// true:表示已经消费了 不会继续传递
// false:表示自己不消费 事件还要继续往下传
// 系统自动将事件包装为MotionEvent类
// 用户可以获取事件的行为:getAction
// ACTION_DOWN ACTION_MOVE ACTION_UP ACTION_CANCEL
// 获取触摸点的坐标 :getX getY
2.重写onTouchEvent方法
public class MainActivity extends AppCompatActivity {
View redView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 通过id找到对应的控件
redView = findViewById(R.id.view);
}
// 重写触摸回调事件onTouchEvent
// 返回值表示这个事件是否已经被处理
// true:表示已经消费了 不会继续传递
// false:表示自己不消费 事件还要继续往下传
// 系统自动将事件包装为MotionEvent类
// 用户可以获取事件的行为:getAction
// ACTION_DOWN ACTION_MOVE ACTION_UP ACTION_CANCEL
// 获取触摸点的坐标 :getX getY
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取事件对应的类型
int action = event.getAction();
// 获取屏幕的大小
Point p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
// 获取容器本身的宽高
RelativeLayout rl = findViewById(R.id.root);
// 计算界面和屏幕之间的间距
// 计算状态栏和标题栏的高度
int padding = p.y - rl.getHeight();
if (action == MotionEvent.ACTION_DOWN) {
// 按下
// 获取触摸点的x坐标和y坐标
float x = event.getX();// 相对于父视图的横坐标
float y = event.getY()-padding;
// event.getRawX(); // 相对于屏幕的横坐标
// 改变控件的位置
redView.setX(x-(float)(redView.getWidth()*0.5));
redView.setY(y-(float)(redView.getHeight()*0.5));
}else if(action == MotionEvent.ACTION_MOVE){
// 滑动
// 获取触摸点的x坐标和y坐标
float x = event.getX();// 相对于父视图的横坐标
float y = event.getY()-padding;
// event.getRawX(); // 相对于屏幕的横坐标
// 改变控件的位置
redView.setX(x-(float)(redView.getWidth()*0.5));
redView.setY(y-(float)(redView.getHeight()*0.5));
}else if(action == MotionEvent.ACTION_UP){
// 离开屏幕
}else {
// 被打断了
}
return true;
}
}
运行效果- 图案解锁demo
xml背景布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:id="@+id/root_layout"
>
<!--背景图片-->
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/main_bg"
android:scaleType="fitXY"/>
<!--9个点的背景图片-->
<ImageView
android:id="@+id/opView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/op_bg"
android:layout_centerInParent="true"/>
<TextView
android:id="@+id/tv_alert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="图案解锁"
android:textSize="20sp"
android:textColor="#ffffff"
android:textAlignment="center"
android:layout_alignTop="@+id/opView"
android:layout_marginTop="90dp"/>
</RelativeLayout>
代码
public class MainActivity extends AppCompatActivity {
// 定义一个数组保存控件
ArrayList<ImageView> dotsList ;
ArrayList<Integer> lineTagsList;
ArrayList<ImageView> selectedList;
int tag;
// 保存上一次被点亮的点的对象
ImageView lastSelectedDot;
// 记录滑动的密码
StringBuilder password;
// 保存原始密码
String orgPassword;
// 保存第一次输入的密码
String firstPassword;
// 提示的文本视图
TextView alertTextView;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// 判断是否已经显示
if (hasFocus){
// 获取容器
RelativeLayout rl = findViewById(R.id.root_layout);
// 获取背景视图
ImageView iv = findViewById(R.id.opView);
// 获取x和y坐标
int x = iv.getLeft();
int y = iv.getTop();
// 获取屏幕密度
float density = getResources().getDisplayMetrics().density;
tag = 14;
// 创建竖线
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
// 创建一个视图用于显示线
ImageView lineView = new ImageView(this);
// 设置图片
lineView.setBackgroundResource(R.drawable.normal_highlight2);
lineView.setVisibility(lineView.INVISIBLE);
lineView.setTag(tag);
lineTagsList.add(tag); // 保存线的tag值
tag += 11;
// 创建布局参数
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin =(int)(x + 42*density+99*density*j) ;
params.topMargin = (int)(y + 170*density+99*density*i);
rl.addView(lineView,params);
System.out.println("n");
}
}
tag = 12;
// 创建横线
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
ImageView lineView = new ImageView(this);
lineView.setBackgroundResource(R.drawable.normal_highlight1);
lineView.setVisibility(lineView.INVISIBLE);
lineView.setTag(tag);
lineTagsList.add(tag); // 保存线的tag值
tag += 11; // 同一行相差11
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = (int)(x + 42*density)+(int)(99*density*j);
params.topMargin = (int)(y + 170*density)+(int)(99*density*i);
rl.addView(lineView,params);
}
// 换一行 相差22
tag += 11;
}
// 创建斜线
// 左斜
//24 35
//57 68
tag = 24;
int rTag = 15;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
// 创建一个视图用于显示线
ImageView lineView = new ImageView(this);
lineView.setTag(rTag);
lineTagsList.add(rTag); // 保存线的tag值
rTag += 11;
lineView.setVisibility(lineView.INVISIBLE);
// 设置图片
lineView.setBackgroundResource(R.drawable.normal_highlight3);
// 创建布局参数
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin =(int)(x + 42*density)+(int)(99*density*j) ;
params.topMargin = (int)(y + 170*density)+(int)(99*density*i);
rl.addView(lineView,params);
ImageView LlineView = new ImageView(this);
LlineView.setTag(tag);
lineTagsList.add(tag); // 保存线的tag值
tag += 11;
// 设置图片
LlineView.setBackgroundResource(R.drawable.normal_highlight4);
LlineView.setVisibility(LlineView.INVISIBLE);
params.leftMargin =(int)(x + 53.3*density)+(int)(99*density*j) ;
params.topMargin = (int)(y + 170*density)+(int)(99*density*i);
rl.addView(LlineView,params);
}
// 换一行 相差22
tag += 11;
rTag += 11;
}
tag = 1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
// 创建用于显示点的视图
ImageView dotView = new ImageView(this);
// 设置对应的tag值
dotView.setTag(tag++);
// 显示对应的图片
dotView.setBackgroundResource(R.drawable.selected_dot);
// 隐藏视图
dotView.setVisibility(View.INVISIBLE);
// 创建控件的尺寸
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = (int)(x + 35*density)+(int)(99*density*j);
params.topMargin = (int)(y + 164*density)+(int)(99*density*i);
// 将控件添加到容器中
rl.addView(dotView,params);
// 将这个控件添加到数组里面
dotsList.add(dotView);
}
}
}
}
// 监听触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
/**
* 点亮每个点:
* 将所有的点的对象保存到数组,当每次touch的时候就能遍历到这个数组
* 的每个控件
*/
// 获取事件类型
int action = event.getAction();
ImageView selected;
float x;
float y;
// 判断是什么事件
switch (action){
case MotionEvent.ACTION_DOWN:
// 按下
// 获取触摸点的坐标
x = event.getX();
y = event.getY();
// 判断x y 是不是在某个点的范围内
selected = dotOfTouch(x, y);
// 点亮
if (selected != null) {
selected.setVisibility(View.VISIBLE);
// 记录下这个点
lastSelectedDot = selected;
// 将tag值拼接到密码中
password.append(selected.getTag());
selectedList.add(selected);
}
break;
case MotionEvent.ACTION_MOVE:
// 移动
// 获取触摸点的坐标
x = event.getX();
y = event.getY();
// 判断x y 是不是在某个点的范围内
selected = dotOfTouch(x, y);
// 点亮
if (selected != null) {
// 判断这个点是不是第一个点
if (lastSelectedDot == null) {
// 第一个点
selected.setVisibility(View.VISIBLE);
// 记录
lastSelectedDot = selected;
}else {
// 不是第一个点
// 获取上一个点 和当前点的tag 组成的线的tag
int lTag =(Integer)lastSelectedDot.getTag();
int cTag = (Integer)selected.getTag();
// 获取两个线的tag small * 10 + big
int lineTag = lTag > cTag ? cTag*10+lTag:lTag*10+cTag;
// 判断这条线是否存在
if (lineTagsList.contains(lineTag)){
// 线存在
// 点亮点
selected.setVisibility(View.VISIBLE);
// 将tag值拼接到密码中
password.append(selected.getTag());
// 将点亮的点添加到数组中
selectedList.add(selected);
// 点亮这条线
// 获取容器对象
RelativeLayout rl = findViewById(R.id.root_layout);
// 通过tag查找子控件
ImageView iv = rl.findViewWithTag(lineTag);
// 点亮线
iv.setVisibility(View.VISIBLE);
// 记录这个点
lastSelectedDot = selected;
// 将点亮的点添加到数组中
selectedList.add(selected);
selectedList.add(iv);
}
}
}
break;
case MotionEvent.ACTION_UP:
// 离开
// 1.绘制密码 和 原始密码比较
// 2.设置密码 第一次
// 3.设置密码 第二次
if(orgPassword != null){
// 有密码了
if (password.toString().equals(orgPassword)){
alertTextView.setText("解锁密码成功");
}else {
alertTextView.setText("解锁密码失败");
}
}else{
// 设置密码
// 判断是第一次还是第二次确认密码
if (firstPassword == null){
// 设置密码的第一次
firstPassword = password.toString();
// 提示确认密码
alertTextView.setText("请确认密码图案");
}else{
// 第二次确认密码
// 判断两次是否一致
if (firstPassword.equals(password.toString())){
// 设置成功
alertTextView.setText("设置密码成功");
// 保存密码
SharedPreferences sp = getSharedPreferences("password", 0);
SharedPreferences.Editor editor = sp.edit();
editor.putString("pwd",firstPassword);
// 保存
editor.commit();
}else {
// 设置失败
alertTextView.setText("两次密码不一致 请重新设置");
firstPassword = null;
}
}
}
System.out.println(password.toString());
clean();
break;
default:
break;
}
return true;
}
public void clean(){
password.setLength(0);
// 隐藏所有选中的视图 点 线
for(ImageView iv:selectedList){
iv.setVisibility(View.INVISIBLE);
}
selectedList.clear();
}
// 写一个方法 处理 判断触摸点是否在某个控件内部
public ImageView dotOfTouch(float x,float y){
/*
1.将触摸点换算到控件的父视图中
// 计算状态栏或者标题栏的距离
Point p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
// 获取容器本身的宽高
RelativeLayout rl = findViewById(R.id.root_layout);
float padding = p.y - rl.getHeight();
*/
/*
// 2. 将控件切换到屏幕坐标系
ImageView first = dotsList.get(0);
int[] loc = new int[2];
first.getLocationOnScreen(loc);
*/
// 遍历数组
for(ImageView dot :dotsList){
// 获取dot相对于屏幕的x y
int[] loc = new int[2];
dot.getLocationOnScreen(loc);
int dx = loc[0];
int dy = loc[1];
// 获取左右边的偏移量
int r = dx + dot.getWidth();
// 获取最底部的偏移量
int b = dy + dot.getHeight();
// 判断这个点是否在这个范围内
if ((x <= r && x>= dx) && (y <= b && y >= dy))
{
return dot;
}
}
return null;
}
/**
* 安卓 在容器中添加的控件需要被window计算/测量
* window -> viewGroup -> 子控件
* 通常在onCreate、onStart、onResume无法获取到控件本身的尺寸
* 所有的测量都是在另外一个线程操作
* 如果想要获取控件的尺寸
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 准备好数组 实例化对象
dotsList = new ArrayList<>();
lineTagsList = new ArrayList<>();
password = new StringBuilder();
selectedList = new ArrayList<>();
// 获取xml文本控件
alertTextView = findViewById(R.id.tv_alert);
// 获取偏好设置对象 整个程序共享 单例
// key-value Map
// name:文件的路径
// mode:模式
// 只能读
// SharedPreferences sp = getSharedPreferences("abc", MODE_PRIVATE);
// // 如果需要存储数据 必须获取Editor对象
// SharedPreferences.Editor editor = sp.edit();
// editor.putString("pwd","123");
//
// // 保存
// editor.commit(); // 立刻保存
// editor.apply(); // 异步 让一个线程处理保存 不是马上
// 查找偏好设置里面是否有保存的密码pwd
![![GIF.gif](https://upload-images.jianshu.io/upload_images/18961452-7a6d533e1f25955b.gif?imageMogr2/auto-orient/strip)
](https://upload-images.jianshu.io/upload_images/18961452-41e0a7a59cc0d79d.gif?imageMogr2/auto-orient/strip)
SharedPreferences sp = getSharedPreferences("password", MODE_PRIVATE);
// 获取pwd对应密码
orgPassword = sp.getString("pwd",null);
if (orgPassword == null){
alertTextView.setText("请设置密码图案");
}else {
alertTextView.setText("请绘制密码图案");
}
}
}
效果