一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Android - Android View自定義鎖屏圖案

Android View自定義鎖屏圖案

2022-02-28 15:30星火燎原2016 Android

這篇文章主要為大家詳細介紹了Android View自定義鎖屏圖案,具有一定的參考價值,感興趣的小伙伴們可以參考一下

前言

Android 自定義 View 技能是成為高級工程師所必備的,筆者覺得自定義 View 沒有什么捷徑可走,唯有經常練習才能解決產品需求。筆者也好久沒有寫自定義 View 了,趕緊寫個控件找點感覺回來。

本文實現的是一個 鎖屏圖案的自定義控件。效果圖如下:

Github 地址:AndroidSample

Android View自定義鎖屏圖案

LockView 介紹

自定義屬性:

Android View自定義鎖屏圖案

引用方式:

(1) 在布局文件中引入

?
1
2
3
4
5
6
7
8
9
<com.xing.androidsample.view.LockView
  android:id="@+id/lock_view"
  app:rowCount="4"
  app:normalColor=""
  app:moveColor=""
  app:errorColor=""
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_margin="40dp" />

(2) 在代碼中設置正確的圖案,用于校驗是否匹配成功,并在回調中獲取結果

?
1
2
3
4
5
6
7
8
9
10
11
12
List<Integer> intList = new ArrayList<>();
  intList.add(3);
  intList.add(7);
  intList.add(4);
  intList.add(2);
  lockView.setStandard(intList);
  lockView.setOnDrawCompleteListener(new LockView.OnDrawCompleteListener() {
   @Override
   public void onComplete(boolean isSuccess) {
    Toast.makeText(CustomViewActivity.this, isSuccess ? "success" : "fail", Toast.LENGTH_SHORT).show();
   }
  });

實現思路

  1. 以默認狀態繪制 rowCount * rowCount 個圓,外圓顏色需要在內圓顏色上加上一定的透明度。
  2. 在 onTouchEvent() 方法中,判斷當前觸摸點與各個圓的圓心距離是否小于圓的半徑,決定各個圓此時處于哪個狀態(normal,move,error),調用 invalidate() 重新繪制,更新顏色。
  3. ?將手指滑動觸摸過的圓的坐標添加到一個 ArrayList 中,使用 Path 連接該集合中選中的圓,即可繪制出劃過的路徑線。

實現步驟

自定義屬性

在 res/values 目錄下新建 attrs.xml 文件:

?
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="LockView">
  <attr name="normalColor" format="color|reference" /> <!--默認圓顏色-->
  <attr name="moveColor" format="color|reference" />  <!--手指觸摸選中圓顏色-->
  <attr name="errorColor" format="color|reference" />  <!--手指抬起錯誤圓顏色-->
  <attr name="rowCount" format="integer" />    <!--每行每列圓的個數-->
 </declare-styleable>
</resources>

獲取自定義屬性

?
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
public LockView(Context context) {
   this(context, null);
  }
 
  public LockView(Context context, @Nullable AttributeSet attrs) {
   super(context, attrs);
   readAttrs(context, attrs);
   init();
  }
 
 /**
 * 獲取自定義屬性
 */
  private void readAttrs(Context context, AttributeSet attrs) {
   TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);
   normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);
   moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);
   errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);
   rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);
   typedArray.recycle();
  }
 
 /**
 * 初始化
 */
  private void init() {
   stateSparseArray = new SparseIntArray(rowCount * rowCount);
   points = new PointF[rowCount * rowCount];
 
   innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   innerCirclePaint.setStyle(Paint.Style.FILL);
 
   outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   outerCirclePaint.setStyle(Paint.Style.FILL);
 
   linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
   linePaint.setStyle(Paint.Style.STROKE);
   linePaint.setStrokeCap(Paint.Cap.ROUND);
   linePaint.setStrokeJoin(Paint.Join.ROUND);
   linePaint.setStrokeWidth(30);
   linePaint.setColor(moveColor);
  }

計算圓的半徑

設定外圓半徑和相鄰兩圓之間間距相同,內圓半徑是外圓半徑的一半,所以半徑計算方式為:

?
1
radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;

設置各圓坐標

各圓坐標使用一維數組保存,計算方式為:

?
1
2
3
4
5
// 各個圓設置坐標點
for (int i = 0; i < rowCount * rowCount; i++) {
  points[i] = new PointF(0, 0);
  points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);
}

測量 View 寬高

根據測量模式設置控件的寬高,當布局文件中設置的是 wrap_content ,默認將控件寬高設置為 600dp

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 int width = getSize(widthMeasureSpec);
 int height = getSize(heightMeasureSpec);
 setMeasuredDimension(width, height);
}
 
private int getSize(int measureSpec) {
 int mode = MeasureSpec.getMode(measureSpec);
 int size = MeasureSpec.getSize(measureSpec);
 if (mode == MeasureSpec.EXACTLY) {
  return size;
 } else if (mode == MeasureSpec.AT_MOST) {
  return Math.min(size, dp2Px(600));
 }
 return dp2Px(600);
}

onTouchEvent() 觸摸事件

在手指滑動過程中,根據當前觸摸點坐標是否落在圓的范圍內,更新該圓的狀態,在重新繪制時,繪制成新的顏色。手指抬起時,將存放狀態的 list,選中圓的 list ,linePath 重置,并將結果回調出來。

 

?
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
private PointF touchPoint;
 
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    reset();
   case MotionEvent.ACTION_MOVE:
    if (touchPoint == null) {
     touchPoint = new PointF(event.getX(), event.getY());
    } else {
     touchPoint.set(event.getX(), event.getY());
    }
    for (int i = 0; i < rowCount * rowCount; i++) {
     // 是否觸摸在圓的范圍內
     if (getDistance(touchPoint, points[i]) < radius) {
      stateSparseArray.put(i, STATE_MOVE);
      if (!selectedList.contains(points[i])) {
       selectedList.add(points[i]);
      }
      break;
     }
    }
    break;
   case MotionEvent.ACTION_UP:
    if (check()) { // 正確圖案
     if (listener != null) {
      listener.onComplete(true);
     }
     for (int i = 0; i < stateSparseArray.size(); i++) {
      int index = stateSparseArray.keyAt(i);
      stateSparseArray.put(index, STATE_MOVE);
     }
    } else // 錯誤圖案
     for (int i = 0; i < stateSparseArray.size(); i++) {
      int index = stateSparseArray.keyAt(i);
      stateSparseArray.put(index, STATE_ERROR);
     }
     linePaint.setColor(0xeeff0000);
     if (listener != null) {
      listener.onComplete(false);
     }
    }
    touchPoint = null;
    if (timer == null) {
     timer = new Timer();
    }
    timer.schedule(new TimerTask() {
     @Override
     public void run() {
      linePath.reset();
      linePaint.setColor(0xee0000ff);
      selectedList.clear();
      stateSparseArray.clear();
      postInvalidate();
     }
    }, 1000);
    break;
  }
  invalidate();
  return true;
 }

繪制各圓和各圓之間連接線段

?
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
@Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  drawCircle(canvas);
  drawLinePath(canvas);
 }
 
 private void drawCircle(Canvas canvas) {
  // 依次從索引 0 到索引 8,根據不同狀態繪制圓點
  for (int index = 0; index < rowCount * rowCount; index++) {
   int state = stateSparseArray.get(index);
   switch (state) {
    case STATE_NORMAL:
     innerCirclePaint.setColor(normalColor);
     outerCirclePaint.setColor(normalColor & 0x66ffffff);
     break;
    case STATE_MOVE:
     innerCirclePaint.setColor(moveColor);
     outerCirclePaint.setColor(moveColor & 0x66ffffff);
     break;
    case STATE_ERROR:
     innerCirclePaint.setColor(errorColor);
     outerCirclePaint.setColor(errorColor & 0x66ffffff);
     break;
   }
   canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);
   canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);
  }
 }

完整 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
/**
 * Created by star.tao on 2018/5/30.
 * email: [email protected]
 * github: https://github.com/xing16
 */
 
public class LockView extends View {
 
 private static final int DEFAULT_NORMAL_COLOR = 0xee776666;
 private static final int DEFAULT_MOVE_COLOR = 0xee0000ff;
 private static final int DEFAULT_ERROR_COLOR = 0xeeff0000;
 private static final int DEFAULT_ROW_COUNT = 3;
 
 private static final int STATE_NORMAL = 0;
 private static final int STATE_MOVE = 1;
 private static final int STATE_ERROR = 2;
 
 
 private int normalColor; // 無滑動默認顏色
 private int moveColor; // 滑動選中顏色
 private int errorColor; // 錯誤顏色
 
 private float radius; // 外圓半徑
 
 private int rowCount;
 
 private PointF[] points; // 一維數組記錄所有圓點的坐標點
 
 private Paint innerCirclePaint; // 內圓畫筆
 
 private Paint outerCirclePaint; // 外圓畫筆
 
 private SparseIntArray stateSparseArray;
 
 private List<PointF> selectedList = new ArrayList<>();
 
 private List<Integer> standardPointsIndexList = new ArrayList<>();
 
 private Path linePath = new Path(); // 手指移動的路徑
 
 private Paint linePaint;
 
 private Timer timer;
 
 public LockView(Context context) {
  this(context, null);
 }
 
 public LockView(Context context, @Nullable AttributeSet attrs) {
  super(context, attrs);
  readAttrs(context, attrs);
  init();
 }
 
 
 private void readAttrs(Context context, AttributeSet attrs) {
  TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);
  normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);
  moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);
  errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);
  rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);
  typedArray.recycle();
 }
 
 private void init() {
  stateSparseArray = new SparseIntArray(rowCount * rowCount);
  points = new PointF[rowCount * rowCount];
 
  innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  innerCirclePaint.setStyle(Paint.Style.FILL);
 
  outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  outerCirclePaint.setStyle(Paint.Style.FILL);
 
 
  linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  linePaint.setStyle(Paint.Style.STROKE);
  linePaint.setStrokeCap(Paint.Cap.ROUND);
  linePaint.setStrokeJoin(Paint.Join.ROUND);
  linePaint.setStrokeWidth(30);
  linePaint.setColor(moveColor);
 
 }
 
 
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh);
  // 外圓半徑 = 相鄰外圓之間間距 = 2倍內圓半徑
  radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
  // 各個圓設置坐標點
  for (int i = 0; i < rowCount * rowCount; i++) {
   points[i] = new PointF(0, 0);
   points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);
  }
 }
 
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int width = getSize(widthMeasureSpec);
  int height = getSize(heightMeasureSpec);
  setMeasuredDimension(width, height);
 }
 
 private int getSize(int measureSpec) {
  int mode = MeasureSpec.getMode(measureSpec);
  int size = MeasureSpec.getSize(measureSpec);
  if (mode == MeasureSpec.EXACTLY) {
   return size;
  } else if (mode == MeasureSpec.AT_MOST) {
   return Math.min(size, dp2Px(600));
  }
  return dp2Px(600);
 }
 
 
 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  drawCircle(canvas);
  drawLinePath(canvas);
 }
 
 private void drawCircle(Canvas canvas) {
  // 依次從索引 0 到索引 8,根據不同狀態繪制圓點
  for (int index = 0; index < rowCount * rowCount; index++) {
   int state = stateSparseArray.get(index);
   switch (state) {
    case STATE_NORMAL:
     innerCirclePaint.setColor(normalColor);
     outerCirclePaint.setColor(normalColor & 0x66ffffff);
     break;
    case STATE_MOVE:
     innerCirclePaint.setColor(moveColor);
     outerCirclePaint.setColor(moveColor & 0x66ffffff);
     break;
    case STATE_ERROR:
     innerCirclePaint.setColor(errorColor);
     outerCirclePaint.setColor(errorColor & 0x66ffffff);
     break;
   }
   canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);
   canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);
  }
 }
 
 /**
  * 繪制選中點之間相連的路徑
  *
  * @param canvas
  */
 private void drawLinePath(Canvas canvas) {
  // 重置linePath
  linePath.reset();
  // 選中點個數大于 0 時,才繪制連接線段
  if (selectedList.size() > 0) {
   // 起點移動到按下點位置
   linePath.moveTo(selectedList.get(0).x, selectedList.get(0).y);
   for (int i = 1; i < selectedList.size(); i++) {
    linePath.lineTo(selectedList.get(i).x, selectedList.get(i).y);
   }
   // 手指抬起時,touchPoint設置為null,使得已經繪制游離的路徑,消失掉,
   if (touchPoint != null) {
    linePath.lineTo(touchPoint.x, touchPoint.y);
   }
   canvas.drawPath(linePath, linePaint);
  }
 }
 
 private PointF touchPoint;
 
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN:
    reset();
   case MotionEvent.ACTION_MOVE:
    if (touchPoint == null) {
     touchPoint = new PointF(event.getX(), event.getY());
    } else {
     touchPoint.set(event.getX(), event.getY());
    }
    for (int i = 0; i < rowCount * rowCount; i++) {
     // 是否觸摸在圓的范圍內
     if (getDistance(touchPoint, points[i]) < radius) {
      stateSparseArray.put(i, STATE_MOVE);
      if (!selectedList.contains(points[i])) {
       selectedList.add(points[i]);
      }
      break;
     }
    }
    break;
   case MotionEvent.ACTION_UP:
    if (check()) { // 正確圖案
     if (listener != null) {
      listener.onComplete(true);
     }
     for (int i = 0; i < stateSparseArray.size(); i++) {
      int index = stateSparseArray.keyAt(i);
      stateSparseArray.put(index, STATE_MOVE);
     }
    } else // 錯誤圖案
     for (int i = 0; i < stateSparseArray.size(); i++) {
      int index = stateSparseArray.keyAt(i);
      stateSparseArray.put(index, STATE_ERROR);
     }
     linePaint.setColor(0xeeff0000);
     if (listener != null) {
      listener.onComplete(false);
     }
    }
    touchPoint = null;
    if (timer == null) {
     timer = new Timer();
    }
    timer.schedule(new TimerTask() {
     @Override
     public void run() {
      linePath.reset();
      linePaint.setColor(0xee0000ff);
      selectedList.clear();
      stateSparseArray.clear();
      postInvalidate();
     }
    }, 1000);
    break;
  }
  invalidate();
  return true;
 }
 
 /**
  * 清除繪制圖案的條件,當觸發 invalidate() 時將清空圖案
  */
 private void reset() {
  touchPoint = null;
  linePath.reset();
  linePaint.setColor(0xee0000ff);
  selectedList.clear();
  stateSparseArray.clear();
 }
 
 
 public void onStop() {
  timer.cancel();
 }
 
 private boolean check() {
  if (selectedList.size() != standardPointsIndexList.size()) {
   return false;
  }
  for (int i = 0; i < standardPointsIndexList.size(); i++) {
   Integer index = standardPointsIndexList.get(i);
   if (points[index] != selectedList.get(i)) {
    return false;
   }
  }
  return true;
 }
 
 public void setStandard(List<Integer> pointsList) {
  if (pointsList == null) {
   throw new IllegalArgumentException("standard points index can't null");
  }
  if (pointsList.size() > rowCount * rowCount) {
   throw new IllegalArgumentException("standard points index list can't large to rowcount * columncount");
  }
  standardPointsIndexList = pointsList;
 }
 
 private OnDrawCompleteListener listener;
 
 public void setOnDrawCompleteListener(OnDrawCompleteListener listener) {
  this.listener = listener;
 }
 
 
 public interface OnDrawCompleteListener {
  void onComplete(boolean isSuccess);
 }
 
 
 private float getDistance(PointF centerPoint, PointF downPoint) {
  return (float) Math.sqrt(Math.pow(centerPoint.x - downPoint.x, 2) + Math.pow(centerPoint.y - downPoint.y, 2));
 
 }
 
 private int dp2Px(int dpValue) {
  return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
 }
 
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/xingxtao/article/details/80545120

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 调教人妖| 果冻传媒新在线观看免费 | 成人精品一区二区三区中文字幕 | 四虎永久在线精品国产馆v视影院 | 国产拍拍拍免费专区在线观看 | 91在线老王精品免费播放 | 日韩专区 | 干b视频在线观看 | 午夜影院h | 亚洲人成综合在线播放 | 国产午夜永久福利视频在线观看 | 国产成人精品在线观看 | 4tube高清性欧美 | 天天色天| 欧美日韩一区二区三区在线视频 | 久久久久久久电影 | 国产成人一区二区三区视频免费蜜 | jiujiure精品 | 国产福利在线免费观看 | 武侠古典久久亚洲精品 | 色先锋av资源中文字幕 | 好湿好紧好多水c | 好紧好爽范冰冰系列 | 午夜精品久久久久久久2023 | 欧美伊香蕉久久综合类网站 | 日本久久免费大片 | 视频网站入口在线看 | caoporen在线视频入口 | 精品蜜臀AV在线天堂 | 欧美一级xxxx俄罗斯一级 | a级片在线观看免费 | 日本三级免费观看 | 久久精品亚洲国产AV涩情 | 19+韩国女主播激情vip视频在线 | 国产高清不卡视频在线播放 | 亚洲国产精品自在在线观看 | 亚洲ⅴa偷拍在线影院 | 免费日本在线视频 | 欧美特黄一级大片 | 男人肌肌捅女人 | 亚洲免费福利视频 |