手机滑动解锁项目

(一)知识准备

事件处理机制:

什么是事件处理机制?比如我们点击QQ登录界面上的登录按钮,我们就向服务器发送了一个登录请求;再比如,我们点击屏幕选择操作,计算机就为我们处理了我们处理了我们所选择的操作。简单的说就是实现我们手机UI界面上显示的功能。

(一)基于监听的事件处理机制

事件监听机制中由事件源,事件,事件监听器三类对象组成 处理流程如下:

1,为某个事件源(组件)设置一个监听器,用于监听用户操作
2,用户的操作,触发了事件源的监听器
3,生成了对应的事件对象
4,将这个事件源对象作为参数传给事件监听器
5,事件监听器对事件对象进行判断,执行对应的事件处理器(对应事件的处理方法)

菜鸟教程
(二)基于回调的事件处理机制

菜鸟教程

ps:我们要做的手机滑动解锁项目就是采用这种事件处理机制,需要重写onTouchEvent方法:
1,返回值表示事件是否已经被处理,true表示已经被处理了,不会继续传递 ;false表示自己不消费,事件还有继续往下传递;
2,系统自动将事件包装为MotionEvent类;
3用户可以获取事件的行为 :getAction
Action_DOWN ,ACTION_MOVE, ACTION_UP ,ACTION_CANCEL ,ACTION_OUTSIDE;
4获取触摸点的坐标:getX:相对于父视图的坐标,getRawX:相对于屏幕的坐标;
代码示例:在屏幕上显示一个小方块,我们手点到哪里,它就移动到哪里。
activity_main.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">

<View
    android:id="@+id/v1"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:background="@color/colorPrimary"
    />
</RelativeLayout>

重写onTouchEvent方法

public class MainActivity extends AppCompatActivity {
View view;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    //通过id找到对应的控件
    view = findViewById(R.id.v1);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    //获取事件对应的类型
    int action = event.getAction();
    //获取屏幕的大小
    Point p = new Point();
    getWindowManager().getDefaultDisplay().getSize(p);
    //获取容器本身的高度
    RelativeLayout rl = findViewById(R.id.root);

    //计算屏幕和屏幕之间的差距
    float padding = p.y - rl.getHeight();
    //判断事件类型
    if(action == MotionEvent.ACTION_DOWN){
        //按下
        //获取触摸点的x,y坐标
        float x = event.getX();
        float y = event.getY() - padding;
        //改变控件中的位置
        view.setX(x - (float)(view.getWidth()*0.5));
        view.setY(y - (float)(view.getHeight()*0.5));
    }
    else if(action == MotionEvent.ACTION_MOVE){
        //获取触摸点的x,y坐标
        float x = event.getX();
        float y = event.getY() - padding;
        //改变控件中的位置
        view.setX(x - (float)(view.getWidth()*0.5));
        view.setY(y - (float)(view.getHeight()*0.5));
    }
    else if(action == MotionEvent.ACTION_POINTER_UP){
    }
    return true;
  }
}
效果展示

数据存储和访问

Android的数据存储和访问有四种方式:
1,文件存储
2,SharedPreferences用户偏好参数
3,SQLite数据库
4,netWork网络存储
我们今天要用到的就是SharedPreferences用户偏好参数保存数据。当我们想要记录一些账号密码的话,不需要使用数据库,只需要把这些信息保存在特定的文件中。SharedPreferences是使用xml文件。然后类似于Map集合,使用键-值的形式来存储数据;我们只需要调用SharedPreferences的get方法,就能够根据键获得对应的值。但是SharedPreferences只能读取数据,不能写入数据,如果想要写入数据,需要获得Editor对象,Editor是SharedPreferences的内部类,通过调用Editor对象的方法来实现写入数据:

 SharedPreferences sp = getSharedPreferences("abc",MODE_PRIVATE);
 String result = sp.getString("pwd",null);
 //如果需要存储数据 必须获取Editor对象
 SharedPreferences.Editor editor = sp.edit();
 editor.putString("pwd", "123");
 //保存
 editor.commit(); //⽴刻保存
 editor.apply(); //异步保存, 让⼀个线程处理保存 不是⻢上保存

(二)滑动解锁

一、要求:

设计一个手机滑动解锁程序,可以实现设置密码,解锁,密码错误次数过多是提示用户三分钟再尝试。

二、设计思路:

设计九宫格滑动解锁,需要九个点的图案,还有线的图案,添加完图案之后把所有的图案隐藏起来,当手指在点上滑动时将隐藏的点显示出来,再根据两个点的tag值,把两点之间的线点亮,这样就可以实现滑动;记录密码功能就是,把每个点的tag值按先后顺序拼接成字符串,在把字符串存到SharedPreferences中。
技术难点:
1,如何把快速把九个点和二十条线添加到容器中;
2,怎样为每个点和每条线表上tag值;
3,密码的拼接,保存,对比等;

三、解决方案:

1,采用循环的方式添加每个控件,但是要注意控件的距离问题,可以不断运行,不断调整,直到满意为止;
2,标记tag值,给每个原点控件标上1~9,然后两点之间的线的tag值就是使用两个点的tag值组成一个二位数,就是这条线的tag值;横线和斜线在同一行tag值相差11,换行相差22;


标记tag值

3,密码拼接成字符串类型,保存在SharedPreferences中,读取方便;

四、具体实现:

一、先在activity_main.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:id="@+id/bgImageView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="fitXY"
    android:src="@drawable/main_bg"
    tools:layout_editor_absoluteX="-16dp"
    tools:layout_editor_absoluteY="-41dp" />
<!--九个点的背景图片-->
<ImageView
    android:id="@+id/opView"
    android:layout_width="match_parent"
    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>
效果展示

二、添加九个点和20条线,并为每个点,每条线表上tag值;
1,先要判断背景视图是否已经显示,显示的话,再添加点和线;
2,添加三行两列六条横线,两行三列六条竖线,两行两列四条左斜线,四条右斜线;
3,添加点和线的同时为每个控件标上tag值,方便可以找到通过tag值找到每个控件(代码里的tag已经在上面声明过);
注意:再添加图片的时候,屏幕不同,每个控件的坐标也会不同,为了解决屏幕适配的问题,所以要通过计算屏幕密度来控制每个控件的坐标;

    @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 scale = getResources().getDisplayMetrics().density;
        //创建横线 6条
        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(View.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 + 62*scale) + (int)(105*scale*j);
                params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(lineView, params);
            }
            //换⼀⾏ 相差22
            tag += 11;
        }
        //创建竖线 6条
        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(View.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 + 62*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(lineView, params);
            }
        }
        //创建斜线
        tag = 24;
        int rTag = 15;
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                //创建⼀个视图⽤于显示线
                  //右斜
                ImageView rLineView = new ImageView(this);
                rLineView.setTag(rTag);
                lineTagsList.add(rTag);//保存线的tag值
                rTag += 11;
                //设置图⽚
                rLineView.setBackgroundResource(R.drawable.normal_highlight3);
                //创建布局参数
                rLineView.setVisibility(View.INVISIBLE);
                RelativeLayout.LayoutParams params = new
                        RelativeLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                params.leftMargin = (int)(x + 55*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(rLineView, params);
                //左斜
                ImageView lLineView = new ImageView(this);
                lLineView.setTag(tag);
                lineTagsList.add(tag);//保存线的tag值
                tag += 11;
                lLineView.setVisibility(View.INVISIBLE);
                lLineView.setBackgroundResource(R.drawable.normal_highlight4);
                params.leftMargin = (int)(x + 78*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(lLineView,params);
            }
            tag += 11;
            rTag += 11;
        }
        //创建9个点
        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);
                tag++;
                //隐藏视图
                dotView.setVisibility(View.INVISIBLE);
                //显示对应的图⽚
                dotView.setBackgroundResource(R.drawable.selected_dot);
                //创建控件的尺⼨
                RelativeLayout.LayoutParams params = new
                        RelativeLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                params.leftMargin = (int)(x + 55*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 166*scale) + (int)(99*scale*i);
                //将控件添加到容器中
                rl.addView(dotView, params);
                //将这个控件添加到数组
                dotsList.add(dotView);
            }
        }
    }
}

PS:我们来看一下添加效果(没有隐藏视图的情况下),再添加的过程中,要不断地运行,不断地调整控件的坐标,直到满意为止;

Screenshot_20190826_174754_swu.xwj.myapplication1.jpg

三、接下来就是事件处理了,重写onTouchEvent方法;
1,先把所有的视图隐藏,起来;
2,当点击到一个点的位置,这个视图就不在隐藏,点亮起来

   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;

PS:看一下效果(部分代码,一些变量已经在前面声明过):

效果展示

3,点击可以点亮视图,接下来只需要把上面的代码拷贝到滑动触摸的情况下即可实现滑动点亮视图;

   case MotionEvent.ACTION_MOVE:
            //获取触摸点的坐标
            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;

PS:看一下效果:

1566979414260.gif

注:这里面有一个很关键的地方就是,怎么样才能确定我所触摸的点,就在控件的范围内呢?因此我们就写一个方法专门来判断触摸点是否在控件的范围内,代码里面的注释内容为两种获得控件位置的方法,我们采用了简单的第二种方法;

 //写⼀个⽅法 处理 判断触摸点是否在某个控件内部
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 firt = dotsList.get(0);
 int[] loc = new int[2];
 firt.getLocationOnScreen(loc);
 System.out.println("相对屏幕y:"+loc[1]);
 System.out.println("相对容器y:"+firt.getY());
 System.out.println("原本宽度:"+firt.getWidth());
 */
    //遍历数组
    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;
}

4,接下来就是要点亮两点之间的线了,这就需要用到我们之前标记的tag值了。我们先找到点亮的两个点,由这两个点的tag计算得到两点之间线的tag值,再通过tag值找到这个控件,将他的状态设置为可见,这样划过的线也会被点亮,然后在写一个方法,在手离开时,即滑动结束后,把所有的视图隐藏起来;

      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;
                    //将tag值拼接到密码中
                    password.append(selected.getTag());
                    //将点亮的点添加到数组中
                    selectedList.add(selected);
                } else{
                    //不是第⼀个点
                    //获取上⼀个点和当前点的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());
                        //点亮这条线
                        //获取容器对象
                        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:
     clear();
            break;

clear()方法

    //清空 界面上所有点亮的视图
      public void clear(){
    password.setLength(0);
    //隐藏所有选中的视图 点 线
    for (ImageView iv:selectedList){
        iv.setVisibility(View.INVISIBLE);
    }
    //清空数组
    selectedList.clear();
  }

PS:效果展示

效果展示

5,进行到这里,我们的项目已经完成已大半,接下来就是设置拼接密码,和记录密码,然后再把密码保存起来,等下次在运行起来就可以根据设置的密码来判断用户绘制的密码图案是否正确;
在onCreate方法里面初始化SharedPreferences对象

 //查找偏好设置⾥⾯是否有保存的密码pwd
    SharedPreferences sp =
            getSharedPreferences("password",MODE_PRIVATE);
    //获取pwd对应密码
    orgPassword = sp.getString("pwd",null);
    if (orgPassword == null){
        //如果无原始密码,就设置密码
       alertTextView.setText("请设置密码图案");
    }else{
      //已有原始密码,就提示用户绘制解锁图案
       alertTextView.setText("请绘制密码图案");
    }
}

实现设置密码和判断密码是否正确

   case MotionEvent.ACTION_UP:
            //离开
            // 1.绘制密码 和原始密码⽐较
            // 2.设置密码 第⼀次
            // 3.设置密码 第⼆次
            if (orgPassword != null){
                //有密码了
                if (password.toString().equals(orgPassword)){
                    alertTextView.setText("解锁密码成功");
                } else{
                    alertTextView.setText("解锁密码失败");
                    wrongTime ++;
                    if(wrongTime == 5){
                        alertTextView.setText("错误次数超过5次,锁死手机");
                    }
                }
            } 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;
                    }
                }
            }
            clear();
            break;

PS:效果展示(密码已提前设置好):

完整效果展示

(三)学习感悟

存在的一些问题:
1,比如设置密码两次密码不一致,没有重新返回到设置密码的步骤;
2,错误五次以上,就不能再绘制密码图案这个功能也未实现;
3,判断触摸点的位置是否在控件内部不太精准,存在一些偏差;
感悟:学习做这个项目花了大概两天的时间,老师还要给我穿插的讲一些没有学但需要用到的知识,因此时间还是有一点赶;但是做个这项目才让人真的感觉我在学软件开发,之前学的C语言,Java之类的内容都没有界面,感觉学起来非常的枯燥,但是现在学这个,你写了什么代码,做了什么事,一运行就可以立马看到,我感觉这样非常的有趣,兴趣是最好的老师。这个项目虽然已经基本上算是完成了,但我现在还是没有熟悉的掌握它,写了两遍也只是熟悉了它一下,想要真正的融会贯通,还需要再多联系几次。

(四)完整代码:

import android.content.SharedPreferences;
import android.graphics.Point;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
//定义⼀个数组 保存每个点的控件
ArrayList<ImageView> dotsList;
//保存每条线的tag值
ArrayList<Integer> lineTagsList;
//保存所有被点亮的控件
ArrayList<ImageView> selectedList;
//tag值,为每个控件标号
int tag;
//保存上⼀个被点亮的点
ImageView lastSelectedDot;
//记录滑动的密码
StringBuilder password;
//保存原始密码
String orgPassword;
//设置密码时-保存第⼀次输⼊的密码
String firstPassword;
//提示的⽂本视图
TextView alertTextView;
//记录密码输入错误次数
int wrongTime = 0;
@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 scale = getResources().getDisplayMetrics().density;
        //创建横线 6条
      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(View.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 + 62*scale) + (int)(105*scale*j);
               params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);

                rl.addView(lineView, params);
            }
            //换⼀⾏ 相差22
            tag += 11;
        }
    //创建竖线 6条
        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(View.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 + 62*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(lineView, params);
            }
        }
        //创建斜线
        tag = 24;
        int rTag = 15;
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                //创建⼀个视图⽤于显示线
                ImageView rLineView = new ImageView(this);
                rLineView.setTag(rTag);
                lineTagsList.add(rTag);//保存线的tag值
                rTag += 11;
                //设置图⽚
                rLineView.setBackgroundResource(R.drawable.normal_highlight3);
                //创建布局参数
                rLineView.setVisibility(View.INVISIBLE);
                RelativeLayout.LayoutParams params = new
                        RelativeLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
              params.leftMargin = (int)(x + 55*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(rLineView, params);
                //左斜
                ImageView lLineView = new ImageView(this);
                lLineView.setTag(tag);
                lineTagsList.add(tag);//保存线的tag值
                tag += 11;
                lLineView.setVisibility(View.INVISIBLE);
                lLineView.setBackgroundResource(R.drawable.normal_highlight4);
              params.leftMargin = (int)(x + 78*scale) + (int)(99*scale*j);
              params.topMargin = (int)(y + 170*scale) + (int)(99*scale*i);
                rl.addView(lLineView,params);
            }
            tag += 11;
            rTag += 11;
        }
        //创建9个点
        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);
                tag++;
                //隐藏视图
                dotView.setVisibility(View.INVISIBLE);
                //显示对应的图⽚
                dotView.setBackgroundResource(R.drawable.selected_dot);
                //创建控件的尺⼨
                RelativeLayout.LayoutParams params = new
                        RelativeLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                params.leftMargin = (int)(x + 55*scale) + (int)(99*scale*j);
                params.topMargin = (int)(y + 166*scale) + (int)(99*scale*i);
                //将控件添加到容器中
                rl.addView(dotView, params);
                //将这个控件添加到数组
                dotsList.add(dotView);
            }
        }
    }
}
  //监听触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
    //获取事件的类型
    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;
                    //将tag值拼接到密码中
                    password.append(selected.getTag());
                    //将点亮的点添加到数组中
                    selectedList.add(selected);
                } else{
                    //不是第⼀个点
                    //获取上⼀个点和当前点的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());
                        //点亮这条线
                        //获取容器对象
                        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("解锁密码失败");
                    wrongTime ++;
                    if(wrongTime == 5){
                        alertTextView.setText("错误次数超过5次,锁死手机");
                    }
                }
            } 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;
                    }
                }
            }
            clear();
            break;
        default:
            break;
    }
    return true;
}
//清空 界面上所有点亮的视图
public void clear(){
    password.setLength(0);
    //隐藏所有选中的视图 点 线
    for (ImageView iv:selectedList){
        iv.setVisibility(View.INVISIBLE);
    }
    //清空数组
    selectedList.clear();
}
 public ImageView dotOfTouch(float x, float y){
  //遍历数组
    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;
}
@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);
//查找偏好设置⾥⾯是否有保存的密码pwd
    SharedPreferences sp =
            getSharedPreferences("password",MODE_PRIVATE);
    //获取pwd对应密码
    orgPassword = sp.getString("pwd",null);
    if (orgPassword == null){
       alertTextView.setText("请设置密码图案");
    }else{
       alertTextView.setText("请绘制密码图案");
      }
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,090评论 1 32
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,362评论 0 17
  • 一、简历准备 1、个人技能 (1)自定义控件、UI设计、常用动画特效 自定义控件 ①为什么要自定义控件? Andr...
    lucas777阅读 5,190评论 2 54
  • 小伙伴有没有遇到过在工作汇报中,毫无逻辑,让人听不出重点?一次次的让领导失望,让自己失去信心啦?今天就给大家分享汇...
    咖啡与浓茶阅读 1,099评论 0 0
  • 碧玺(Tourmaline)又称“碧硒”、“碧洗”、“碧霞玺”等,英文名称“Tourmaline”来源于古僧迦罗语...
    一颗挖宝酱阅读 413评论 0 0