您好,登錄后才能下訂單哦!
本篇文章為大家展示了怎么在Android 中利用View自定義鎖屏圖案,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
LockView 介紹
自定義屬性:
引用方式:
(1) 在布局文件中引入
<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) 在代碼中設(shè)置正確的圖案,用于校驗(yàn)是否匹配成功,并在回調(diào)中獲取結(jié)果
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(); } });
實(shí)現(xiàn)思路
以默認(rèn)狀態(tài)繪制 rowCount * rowCount 個(gè)圓,外圓顏色需要在內(nèi)圓顏色上加上一定的透明度。
在 onTouchEvent() 方法中,判斷當(dāng)前觸摸點(diǎn)與各個(gè)圓的圓心距離是否小于圓的半徑,決定各個(gè)圓此時(shí)處于哪個(gè)狀態(tài)(normal,move,error),調(diào)用 invalidate() 重新繪制,更新顏色。
將手指滑動(dòng)觸摸過的圓的坐標(biāo)添加到一個(gè) ArrayList 中,使用 Path 連接該集合中選中的圓,即可繪制出劃過的路徑線。
實(shí)現(xiàn)步驟
自定義屬性
在 res/values 目錄下新建 attrs.xml 文件:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LockView"> <attr name="normalColor" format="color|reference" /> <!--默認(rèn)圓顏色--> <attr name="moveColor" format="color|reference" /> <!--手指觸摸選中圓顏色--> <attr name="errorColor" format="color|reference" /> <!--手指抬起錯(cuò)誤圓顏色--> <attr name="rowCount" format="integer" /> <!--每行每列圓的個(gè)數(shù)--> </declare-styleable> </resources>
獲取自定義屬性
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); }
計(jì)算圓的半徑
設(shè)定外圓半徑和相鄰兩圓之間間距相同,內(nèi)圓半徑是外圓半徑的一半,所以半徑計(jì)算方式為:
radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
設(shè)置各圓坐標(biāo)
各圓坐標(biāo)使用一維數(shù)組保存,計(jì)算方式為:
// 各個(gè)圓設(shè)置坐標(biāo)點(diǎn) 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); }
測(cè)量 View 寬高
根據(jù)測(cè)量模式設(shè)置控件的寬高,當(dāng)布局文件中設(shè)置的是 wrap_content ,默認(rèn)將控件寬高設(shè)置為 600dp
@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() 觸摸事件
在手指滑動(dòng)過程中,根據(jù)當(dāng)前觸摸點(diǎn)坐標(biāo)是否落在圓的范圍內(nèi),更新該圓的狀態(tài),在重新繪制時(shí),繪制成新的顏色。手指抬起時(shí),將存放狀態(tài)的 list,選中圓的 list ,linePath 重置,并將結(jié)果回調(diào)出來。
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++) { // 是否觸摸在圓的范圍內(nè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 { // 錯(cuò)誤圖案 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; }
繪制各圓和各圓之間連接線段
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawCircle(canvas); drawLinePath(canvas); } private void drawCircle(Canvas canvas) { // 依次從索引 0 到索引 8,根據(jù)不同狀態(tài)繪制圓點(diǎn) 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 代碼:
/** * Created by star.tao on 2018/5/30. * email: xing-java@foxmail.com * 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; // 無滑動(dòng)默認(rèn)顏色 private int moveColor; // 滑動(dòng)選中顏色 private int errorColor; // 錯(cuò)誤顏色 private float radius; // 外圓半徑 private int rowCount; private PointF[] points; // 一維數(shù)組記錄所有圓點(diǎn)的坐標(biāo)點(diǎn) private Paint innerCirclePaint; // 內(nèi)圓畫筆 private Paint outerCirclePaint; // 外圓畫筆 private SparseIntArray stateSparseArray; private List<PointF> selectedList = new ArrayList<>(); private List<Integer> standardPointsIndexList = new ArrayList<>(); private Path linePath = new Path(); // 手指移動(dòng)的路徑 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); // 外圓半徑 = 相鄰?fù)鈭A之間間距 = 2倍內(nèi)圓半徑 radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f; // 各個(gè)圓設(shè)置坐標(biāo)點(diǎn) 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,根據(jù)不同狀態(tài)繪制圓點(diǎn) 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); } } /** * 繪制選中點(diǎn)之間相連的路徑 * * @param canvas */ private void drawLinePath(Canvas canvas) { // 重置linePath linePath.reset(); // 選中點(diǎn)個(gè)數(shù)大于 0 時(shí),才繪制連接線段 if (selectedList.size() > 0) { // 起點(diǎn)移動(dòng)到按下點(diǎn)位置 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); } // 手指抬起時(shí),touchPoint設(shè)置為null,使得已經(jīng)繪制游離的路徑,消失掉, 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++) { // 是否觸摸在圓的范圍內(nè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 { // 錯(cuò)誤圖案 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; } /** * 清除繪制圖案的條件,當(dāng)觸發(fā) invalidate() 時(shí)將清空?qǐng)D案 */ 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()); } }
上述內(nèi)容就是怎么在Android 中利用View自定義鎖屏圖案,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。