使用Android自定义控件实现滑动解锁九宫格

时间:2021-09-20 06:36:28

本文概述:

 滑动解锁九宫格的分析:

1、需要自定义控件;
2、需要重写事件ontouchevent();
3、需要给九个点设置序号和坐标,这里用map类就行;
4、需要判断是否到滑到过九点之一,并存储滑到过的点的序号,而且需要一个方法可以返回它们,这里用list类就行;

滑动解锁当前还是比较流行的,今天写了个简单的滑动解锁九宫格的例程,分享出来让初学者看看。

我的是这样的:

使用Android自定义控件实现滑动解锁九宫格

demo

首先,自定义一个view

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
 * 九宫格
 */
public class ninegridview extends view {
  private int width;//该控件的宽
  private int height;//该控件的高
  private paint mpaintbigcircle;//用于画外圆
  private paint mpaintsmallcircle;//用于画内圆
  private paint mpaintline;//用于画线
  private paint mpainttext;//用于画文本
  private path path;//手势划线时需要用到它
  private map<integer, float[]> pointcontainer;//存储九个点的坐标
  private list<integer> pointerslipped;//存储得到的九宫格密码
  public list<integer> getpointerslipped() {
    return pointerslipped;
  }
  public void setpointerslipped(list<integer> pointerslipped) {
    this.pointerslipped = pointerslipped;
  }
  public ninegridview(context context) {
    super(context);
  }
  public ninegridview(context context, attributeset attrs) {
    super(context, attrs);
    mpaintbigcircle = new paint();
    mpaintbigcircle.setcolor(color.blue);
    mpaintbigcircle.setstyle(paint.style.stroke);//不充满
    mpaintbigcircle.setantialias(true);//抗锯齿打开
    mpaintsmallcircle = new paint();
    mpaintsmallcircle.setcolor(color.green);
    mpaintsmallcircle.setstyle(paint.style.fill);//充满,即画的几何体为实心
    mpaintsmallcircle.setantialias(true);
    mpaintline = new paint();
    mpaintline.setcolor(color.green);
    mpaintline.setstyle(paint.style.stroke);
    mpaintline.setstrokewidth(20);
    mpaintline.setantialias(true);
    mpainttext = new paint();
    mpainttext.setcolor(color.white);
    mpainttext.settextalign(paint.align.center);//向*对齐
    mpainttext.settextsize(50);
    mpainttext.setantialias(true);
    path = new path();
    pointcontainer = new hashmap<>();
    pointerslipped = new arraylist<>();
  }
  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    super.onmeasure(widthmeasurespec, heightmeasurespec);
    width = getdefaultsize(getsuggestedminimumwidth(), widthmeasurespec);
    height = getdefaultsize(getsuggestedminimumheight(), heightmeasurespec);
  }
  private float pivotx;//触屏得到的x坐标
  private float pivoty;//触屏得到的y坐标
  private float selectedx;//当前选中的圆点的x坐标
  private float selectedy;//当前选中的圆点的y坐标
  private float selectedxold;//从前选中的圆点的x坐标
  private float selectedyold;//从前选中的圆点的y坐标
  private boolean ishasmoved = false;//用于判断path是否调用过moveto()方法
  @override
  public boolean ontouchevent(motionevent event) {
    switch (event.getaction()) {
      case motionevent.action_down:
        pivotx = event.getx();
        pivoty = event.gety();
        //每次触屏时需要清空一下pointerslipped,即重置密码
        pointerslipped.clear();
        log.d("pointtouched", pivotx + "," + pivoty);
        getselectedpointindex(pivotx, pivoty);
        invalidate();//重绘
        break;
      case motionevent.action_move:
        pivotx = event.getx();
        pivoty = event.gety();
        getselectedpointindex(pivotx, pivoty);
        invalidate();
        break;
      case motionevent.action_up:
        /**
         * 当手指离开屏幕时,重置path
         */
        path.reset();
        ishasmoved = false;
        string indexsequence = "";
        //打印出上一次手势密码的值
        for(int index:pointerslipped){
          indexsequence += "/"+index;
        }
        log.d("index",indexsequence);
        break;
    }
    invalidate();
    return true;
  }
  /**
   * 得到并存储经过的圆点的序号
   * @param pivotx
   * @param pivoty
   */
  private void getselectedpointindex(float pivotx, float pivoty) {
    int index = 0;
    if (pivotx > patternmargin && pivotx < patternmargin + bigcircleradius * 2) {
      if (pivoty > height / 2 && pivoty < height / 2 + bigcircleradius * 2) {
        selectedx = pointcontainer.get(1)[0];
        selectedy = pointcontainer.get(1)[1];
        index = 1;
        log.d("selectedpoint", selectedx + "," + selectedy);
      } else if (pivoty > height / 2 + added && pivoty < height / 2 + added + bigcircleradius * 2) {
        selectedx = pointcontainer.get(4)[0];
        selectedy = pointcontainer.get(4)[1];
        index = 4;
      } else if (pivoty > height / 2 + added * 2 && pivoty < height / 2 + added * 2 + bigcircleradius * 2) {
        selectedx = pointcontainer.get(7)[0];
        selectedy = pointcontainer.get(7)[1];
        index = 7;
      }
    } else if (pivotx > patternmargin + added && pivotx < patternmargin + added + bigcircleradius * 2) {
      if (pivoty > height / 2 && pivoty < height / 2 + bigcircleradius * 2) {
        selectedx = pointcontainer.get(2)[0];
        selectedy = pointcontainer.get(2)[1];
        index = 2;
      } else if (pivoty > height / 2 + added && pivoty < height / 2 + added + bigcircleradius * 2) {
        selectedx = pointcontainer.get(5)[0];
        selectedy = pointcontainer.get(5)[1];
        index = 5;
      } else if (pivoty > height / 2 + added * 2 && pivoty <height / 2 + added * 2 + bigcircleradius * 2) {
        selectedx = pointcontainer.get(8)[0];
        selectedy = pointcontainer.get(8)[1];
        index = 8;
      }
    } else if (pivotx > patternmargin + added * 2 && pivotx < patternmargin + added * 2 + bigcircleradius * 2) {
      if (pivoty > height / 2 && pivoty < height / 2 + bigcircleradius * 2) {
        selectedx = pointcontainer.get(3)[0];
        selectedy = pointcontainer.get(3)[1];
        index = 3;
      } else if (pivoty > height / 2 + added && pivoty < height / 2 + added + bigcircleradius * 2) {
        selectedx = pointcontainer.get(6)[0];
        selectedy = pointcontainer.get(6)[1];
        index = 6;
      } else if (pivoty > height / 2 + added * 2 && pivoty < height / 2 + added * 2 + bigcircleradius * 2) {
        selectedx = pointcontainer.get(9)[0];
        selectedy = pointcontainer.get(9)[1];
        index = 9;
      }
    }
    if (selectedx!=selectedxold||selectedy!=selectedyold){
      //当这次的坐标与上次的坐标不同时存储这次点序号
      pointerslipped.add(index);
      selectedxold = selectedx;
      selectedyold = selectedy;
      if (!ishasmoved){
        //当第一次触碰到九个点之一时,path调用moveto;
        path.moveto(selectedx,selectedy);
        ishasmoved = true;
      }else{
        //path移动至当前圆点坐标
        path.lineto(selectedx,selectedy);
      }
    }
  }
  private string text = "请绘制解锁图案";
  private float x;//绘制的圆形的x坐标
  private float y;//绘制圆形的纵坐标
  private float added;//水平竖直方向每个圆点中心间距
  private float patternmargin = 100;//九宫格距离边界距离
  private float bigcircleradius = 90;//外圆半径
  private float smallcircleradius = 25;//内圆半径
  private int index;//圆点的序号
  @override
  protected void ondraw(canvas canvas) {
    super.ondraw(canvas);
    added = (width - patternmargin * 2) / 3;
    x = patternmargin + added / 2;
    y = added / 2 + height / 2;
    index = 1;
    canvas.drawcolor(color.black);
    canvas.drawtext(text, width / 2, height / 4, mpainttext);
    /**
     * 绘制九个圆点图案
     */
    for (int column = 0; column < 3; column++) {
      for (int row = 0; row < 3; row++) {
        canvas.drawcircle(x, y, bigcircleradius, mpaintbigcircle);
        canvas.drawcircle(x, y, smallcircleradius, mpaintsmallcircle);
        pointcontainer.put(index, new float[]{x, y});
        index++;
        x += added;
      }
      y += added;
      x = patternmargin + added / 2;
    }
    x = patternmargin + added / 2;
    y = added / 2 + height / 2;
    canvas.drawpath(path, mpaintline);
  }
}

为什么要规避重复?

因为在触屏时,会调用很多次ontouchevent()方法,这样存储的手势密码肯定会不准确,我在以上代码中作出了处理,已经避免了重复,看打印信息:

这里写图片描述

显然,密码没有相邻数重复,当然还有一种情况就是手指在两个点之间来回等问题,这种状况也需要避免,这里没有作处理。当然,我做得还不够。。。

自定义view中用到的dp和px互相转换的工具类:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class sizeconvert {
  /**
   * 将dp转换为sp
   */
  public static int dip2px(context context, float dipvalue){
    final float scale = context.getresources().getdisplaymetrics().density;
    return (int)(dipvalue * scale + 0.5f);
  }
  /**
   * sp转dp
   */
  public static int px2dip(context context, float pxvalue){
    final float scale = context.getresources().getdisplaymetrics().density;
    return (int)(pxvalue / scale + 0.5f);
  }
}

主活动:

?
1
2
3
4
5
6
7
public class ninegridactivity extends baseactivity{
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.view_nine_grid);
  }
}

layout中的布局文件view_nine_grid:

?
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
  <com.test.shiweiwei.myproject.selfish_view.ninegridview
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
</linearlayout>

总结

我写的只是最基本的九宫格滑动解密项目,实际用的九宫格解密比这个要复杂,有许多特效和其他更严谨的处理,事件的处理也不是这样草草了事,如果想写得漂亮,还得多花工夫。