九点(九宫格)式手势解锁自定义view


周末闲着没事,写了个手势解锁的view,实现起来也蛮快的,半天多一点时间就完事。把源码和资源贴出来,给大家分享,希望对大家有用。

效果,就跟手机上的九点手势解锁一样,上个图吧:

九点(九宫格)式手势解锁自定义view

 

过程嘛感觉确实没啥好讲的了,涉及的知识以前的博客都说过了,无非就是canva,paint,touch事件这些,画画圆圈画画线条,剩下的就是细节处理逻辑了。都在代码里,所以这里就主要是贴资源吧。

这个自定义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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package com.cc.library.view;
 
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.location.Location;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
 
/**
 * 图案解锁view
 * Created by zhangyu on 2016-07-15 15:05.
 */
public class UnlockView extends View {
    private static final String TAG = "UnlockView";
    //view宽高
    private float width, height;
    //平均宽高(分三份)
    private float averageWidth, averageHeight;
    //九个点的位置数据,从左到右、从上到下 123...789
    Location[] locations = new Location[9];
    //圆圈半径
    private float radius;
    //绘制密码
    private int[] drawingPwd = new int[9];
    //正确的密码
    private int[] rightPwd;
    //画笔
    private Paint whitePaint, cyanPaint;
    //已经绘制过了的点个数
    private int drawedNumber;
    //当前正被触摸的点
    private Location nowTouchedPosition = new Location();
    //监听
    private UnlockListener unlockListener;
 
 
    public void setUnlockListener(UnlockListener unlockListener) {
        this.unlockListener = unlockListener;
    }
 
    public UnlockView(Context context) {
        super(context);
        init();
    }
 
    public UnlockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public UnlockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
 
 
    private void init() {
        ViewTreeObserver vto = getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                height = getHeight();
                width = getWidth();
                Log.d(TAG, "width = " + width + "  ,height = " + height);
                averageWidth = width / 3f;
                averageHeight = height / 3f;
                radius = averageHeight > averageWidth ? averageWidth / 5f : averageHeight / 5f;
 
                initLocation();
                invalidate();
            }
        });
 
        whitePaint = new Paint();
        whitePaint.setAntiAlias(true);
        whitePaint.setColor(Color.parseColor("#ffffff"));
        whitePaint.setStyle(Paint.Style.STROKE);
 
        cyanPaint = new Paint();
        cyanPaint.setAntiAlias(true);
        cyanPaint.setColor(Color.parseColor("#4169E1"));
        cyanPaint.setStyle(Paint.Style.STROKE);
 
        initDrawingPwd();
    }
 
    private void drawStart() {
        drawedNumber = 0;
    }
 
    private void drawOver() {
        //debug
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < drawingPwd.length; i++) {
            sb.append(drawingPwd[i] + ",");
        }
        Log.i(TAG, "drawingPwd:" + sb.toString());
 
        initLocation();
        initDrawingPwd();
        drawedNumber = 0;
        invalidate();
    }
 
    /**
     * 初始化绘制密码
     */
    private void initDrawingPwd() {
        for (int i = 0; i < 9; i++) {
            drawingPwd[i] = -1;
        }
    }
 
    /**
     * 初始化九个点坐标
     */
    private void initLocation() {
        for (int i = 0; i < 9; i++) {
            locations[i] = new Location();
            locations[i].deawed = false;
 
            //纵向1、2、3列x坐标
            if (i % 3 == 0) {
                locations[i].x = averageWidth * 0.5f;
            } else if (i % 3 == 1) {
                locations[i].x = averageWidth * 1.5f;
            } else if (i % 3 == 2) {
                locations[i].x = averageWidth * 2.5f;
            }
 
            //横向1、2、3排y坐标
            if (i / 3 == 0) {
                locations[i].y = averageHeight * 0.5f;
            } else if (i / 3 == 1) {
                locations[i].y = averageHeight * 1.5f;
            } else if (i / 3 == 2) {
                locations[i].y = averageHeight * 2.5f;
            }
        }
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < 9; i++) {
            if (!locations[i].deawed) {//没被画
                whitePaint.setStrokeWidth(4);
                canvas.drawPoint(locations[i].x, locations[i].y, whitePaint);
                whitePaint.setStrokeWidth(1.5f);
                canvas.drawCircle(locations[i].x, locations[i].y, radius, whitePaint);
            } else {//被画过了
                cyanPaint.setStrokeWidth(8);
                canvas.drawPoint(locations[i].x, locations[i].y, cyanPaint);
                cyanPaint.setStrokeWidth(3f);
                canvas.drawCircle(locations[i].x, locations[i].y, radius, cyanPaint);
            }
 
            int lastestDrawedPoint = -1;
            if (drawedNumber > 0)
                lastestDrawedPoint = drawingPwd[drawedNumber - 1];
            if (lastestDrawedPoint != -1) {
                Location lastestDrawedLocation = locations[lastestDrawedPoint];//最新一个被选中的点
                cyanPaint.setStrokeWidth(3f);
                canvas.drawLine(lastestDrawedLocation.x, lastestDrawedLocation.y, nowTouchedPosition.x, nowTouchedPosition.y, cyanPaint);
            }
 
            if (drawedNumber > 1) {
                for (int j = 0; j < drawedNumber - 1; j++) {
                    cyanPaint.setStrokeWidth(3f);
                    canvas.drawLine(locations[drawingPwd[j]].x, locations[drawingPwd[j]].y, locations[drawingPwd[j + 1]].x, locations[drawingPwd[j + 1]].y, cyanPaint);
                }
            }
        }
        super.onDraw(canvas);
    }
 
    float moveX, moveY;
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                drawStart();
                break;
            case MotionEvent.ACTION_MOVE:
                moveX = event.getX();
                moveY = event.getY();
 
                dealPosition(moveX, moveY);
                break;
            case MotionEvent.ACTION_UP:
 
                if (unlockListener != null) {
                    unlockListener.drawOver(drawingPwd);
                    if (verificationPwd(rightPwd)) {
                        unlockListener.isPwdRight(true);
                    } else {
                        unlockListener.isPwdRight(false);
                    }
                }
                drawOver();
                break;
        }
        return true;
    }
 
    private void dealPosition(float nowX, float nowY) {
        nowTouchedPosition.x = nowX;
        nowTouchedPosition.y = nowY;
 
        int nowTouched = getWhichOneBeTouched(nowX, nowY);
        if (nowTouched != -1) {//触摸到了点上
 
            if (!locations[nowTouched].deawed) {//如果这点没被触摸过
                drawingPwd[drawedNumber] = nowTouched;      //记录密码
                drawedNumber++;     //被触摸点数+1
                Log.v(TAG, "nowTouched " + nowTouched + "  ,drawedNumber = " + drawedNumber);
 
            }
            locations[nowTouched].deawed = true;
        }
        invalidate();
    }
 
    private class Location {
        public float x = -1, y = -1;
        public boolean deawed;//是否被画过了
    }
 
    /**
     * 获取被触摸到的点
     *
     * @param x 坐标x点
     * @param y 坐标y点
     * @return 被触摸的坐标点位置 或者-1
     */
    private int getWhichOneBeTouched(float x, float y) {
        for (int i = 0; i < locations.length; i++) {
 
            double lPowX = Math.pow(Math.abs(x - locations[i].x), 2);
            double lPowY = Math.pow(Math.abs(y - locations[i].y), 2);
 
            if (Math.sqrt(lPowX + lPowY) < radius)
                return i;
        }
        return -1;
    }
 
    /**
     * 校验密码是否正确
     *
     * @param rightPwd 正确的密码
     * @return 正确返回true 否则返回false
     */
    public boolean verificationPwd(int[] rightPwd) {
        if (rightPwd == null)
            return false;
 
        for (int i = 0; i < rightPwd.length; i++) {
            if (rightPwd[i] != drawingPwd[i])
                return false;
        }
 
        return true;
    }
 
    /**
     * 获取当前绘制的密码
     *
     * @return
     */
    public int[] getDrawedPwd() {
        return drawingPwd;
    }
 
    /**
     * 设置正确的密码
     *
     * @param rightPwd
     */
    public void setRightPwd(int[] rightPwd) {
        this.rightPwd = rightPwd;
    }
 
    //监听接口
    public interface UnlockListener {
        public void drawOver(int[] pwd);
        public void isPwdRight(boolean isRight);
    }
}
布局(view的宽高你可以随意指定,bg1是效果里那张背景图片):

 

 

?
1
2
3
4
5
<!--?xml version="1.0" encoding="utf-8"?-->
<relativelayout android:background="@drawable/bg1" android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android">
 
    <com.cc.library.view.unlockview android:id="@+id/unlock_view" android:layout_alignparentbottom="true" android:layout_height="360dp" android:layout_width="match_parent">
</com.cc.library.view.unlockview></relativelayout>

使用:

 

 

?
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
package com.sz.china.testmoudule;
 
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
 
import com.cc.library.view.UnlockView;
 
/**
 * Created by zhangyu on 2016-07-15 14:55.
 */
public class TestUnlockViewActivity extends Activity {
 
    private UnlockView unlockView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.unlock_activity);
 
        unlockView = (UnlockView) findViewById(R.id.unlock_view);
 
        //设置回调监听
        unlockView.setUnlockListener(new UnlockView.UnlockListener() {
            @Override
            public void drawOver(int[] pwd) {//绘制完成,获取绘制的密码
                unlockView.getDrawedPwd();
            }
            @Override
            public void isPwdRight(boolean isRight) {//密码是否校验正确
                if(isRight)
                    Toast.makeText(TestUnlockViewActivity.this,"密码正确",Toast.LENGTH_SHORT).show();
                else
                    Toast.makeText(TestUnlockViewActivity.this,"密码错误",Toast.LENGTH_SHORT).show();
            }
        });
 
        int[] pwd = {0,5,7,6};
        unlockView.setRightPwd(pwd);    //设置密码
 
    }
}