先上圖:
java
其實很簡單,不用過多解釋,一點點註釋就夠了。android
Java代碼:canvas
package com.example.graphicunlock; import android.os.Bundle; import android.os.Handler; import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Bitmap.Config; import android.graphics.Paint.Style; import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.RelativeLayout; public class MainActivity extends Activity implements OnTouchListener { private RelativeLayout relativeLayout;// 用來擺放九個圓形 private ImageView view;// 用來繪製解鎖路徑 private Path path;// 劃過的路徑 private Paint paint; private Canvas canvas; private Dot[] array = new Dot[9];// 圓形的數組 private Dot lastDot;// 上一個通過的點 private Bitmap bitmap;// 繪製用的bitmap private boolean drawing = false;// 是否正在畫圖 private int radius = 0;// 圓形半徑 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 鎖定豎屏 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // 不顯示標題欄 requestWindowFeature(Window.FEATURE_NO_TITLE); // 全屏 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); relativeLayout = (RelativeLayout) findViewById(R.id.rela); view = (ImageView) findViewById(R.id.view); view.setOnTouchListener(this); drawDots(); } /** * 放置九個圓形 將九個圓形在屏幕中居中放置,每屏幕的三分之一寬度爲一格,橫豎排各三個,每一個圓寬度是屏幕寬度的1/6 */ protected void drawDots() { int TopMars = (getScreenHeight() - getScreenWidth()) / 2; radius = getScreenWidth() / 12; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams( radius * 2, radius * 2); params.leftMargin = (int) (radius * 4 * (j + 0.25)); params.topMargin = (int) (TopMars + radius * 4 * (i + 0.25)); // 新建半徑爲radius的圓形 Dot dot = new Dot(this, radius); array[i * 3 + j] = dot; relativeLayout.addView(dot, params); } } } /** * 檢查pointF是否在某個圓形範圍內 * * @param point * 要檢查的點 * @return 若是確實在某個圓形範圍內,則返回該圓形,反之返回null */ private Dot hitValidDot(PointF point) { for (int i = 0; i < array.length; i++) { Dot dot = array[i]; if (!dot.getPassed()) { int[] location = { 0, 0 }; dot.getLocationOnScreen(location); if (Math.sqrt((point.x - location[0] - radius) * (point.x - location[0] - radius) + (point.y - location[1] - radius) * (point.y - location[1] - radius)) < radius) { return dot; } } } return null; } /** * 要繪製到的目標圖片上的觸摸事件 本方法裏view.invalidate()並非必須的,有沒有同樣…… */ @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 檢查手機按下的點是否在某個圓形內,若是是則以此圓形爲起點開始繪製圖形 PointF point = new PointF(event.getRawX(), event.getRawY()); Dot dot = hitValidDot(point); if (dot != null) { // 開始繪製 先實例化要繪製的bitmap canvas paint 和繪製的路徑path bitmap = Bitmap.createBitmap(getWindowWidth(), getWindowHeight(), Config.ARGB_8888); canvas = new Canvas(bitmap); paint = new Paint(); path = new Path(); // 獲取此圓形中心點的位置 RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot .getLayoutParams(); PointF startPoint = new PointF(params.leftMargin + radius, params.topMargin + radius); // 將loasDot賦值給dot,並將dot設置爲通過狀態 lastDot = dot; lastDot.drawPassed(); // 將圓形的中心點設置爲路徑的起點 並設置要繪製路徑的顏色的寬度 path.moveTo(startPoint.x, startPoint.y); paint.setARGB(255, 0, 0, 255); paint.setStrokeWidth(8); paint.setStyle(Style.STROKE); // 繪製到屏幕 view.setImageBitmap(bitmap); // 標記爲正在繪圖中 drawing = true; } break; case MotionEvent.ACTION_MOVE: if (drawing) { // 先清空圖片 不然看到的是每次繪製的疊加效果 clear(); // 同MotionEvent.ACTION_DOWN中同樣 檢查是否通過了某一點 PointF point2 = new PointF(event.getRawX(), event.getRawY()); Dot dot2 = hitValidDot(point2); if (dot2 != null) { // 不過有時候兩點之間可能會有第三個點,若是第三個點爲非通過狀態,則將此點設置爲通過狀態 Dot dotBetween = checkDotBetween(lastDot, dot2); if (dotBetween != null) { lastDot = dotBetween; lastDot.drawPassed(); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot2 .getLayoutParams(); path.lineTo(params.leftMargin + radius, params.topMargin + radius); } lastDot = dot2; lastDot.drawPassed(); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) dot2 .getLayoutParams(); path.lineTo(params.leftMargin + radius, params.topMargin + radius); } // 繪製出通過的全部點的路徑 canvas.drawPath(path, paint); // 繪製出上一個點到手指觸摸的位置的路徑 RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) lastDot .getLayoutParams(); canvas.drawLine(params.leftMargin + radius, params.topMargin + radius, event.getX(), event.getY(), paint); view.invalidate(); } break; case MotionEvent.ACTION_UP: if (drawing) { // 手指擡起後,清空並從新繪製全部通過的點的路徑,這樣就會清除上一個點到手指觸摸的位置的路徑了 clear(); canvas.drawPath(path, paint); view.invalidate(); // 繪製完畢,將繪製狀態改成false drawing = false; // 三秒種後重置,放在這僅僅是爲了測試重置功能 new Handler().postDelayed(new Runnable() { @Override public void run() { clearAllDrawing(); } }, 3000); } break; default: break; } return true; } /** * 重置全部爲初始狀態 */ protected void clearAllDrawing() { clear(); for (int i = 0; i < array.length; i++) { Dot dot = array[i]; if (dot != null) { dot.drawNormal(); } } drawing = false; } /** * 查檢兩點之間是否通過第三點,若是是則返回第三點,不然返回null */ protected Dot checkDotBetween(Dot dot1, Dot dot2) { int[] loc1 = { 0, 0 }; int[] loc2 = { 0, 0 }; dot1.getLocationOnScreen(loc1); dot2.getLocationOnScreen(loc2); // 兩點之間的中點 PointF pointF = new PointF((loc1[0] + loc2[0]) / 2 + radius, (loc1[1] + loc2[1]) / 2 + radius); return hitValidDot(pointF); } /** * 清空畫面 */ protected void clear() { if (canvas != null && paint != null) { paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR)); canvas.drawPaint(paint); paint.setXfermode(new PorterDuffXfermode(Mode.SRC)); view.invalidate(); } } /** * @return 屏幕寬度 */ public int getScreenWidth() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); return metrics.widthPixels; } /** * @return 屏幕高度 */ public int getScreenHeight() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); return metrics.heightPixels; } /** * @return 返回窗口內容的寬度,不包括通知欄的標題欄,其實跟getScreenWidth()同樣 */ public int getWindowWidth() { return getWindow().findViewById(Window.ID_ANDROID_CONTENT).getWidth(); } /** * @return 返回窗口內容的高度,不包括通知欄的標題欄,可是在這裏是全屏,因此與getScreenHeight()返回的實際上是一致的 */ public int getWindowHeight() { return getWindow().findViewById(Window.ID_ANDROID_CONTENT).getHeight(); } /** * 圓形 */ public class Dot extends ImageView { private int dotradius = 0;// 圓形半徑 private boolean passed = false;// 是否通過的狀態 public Dot(Context context) { super(context); } public Dot(Context context, int rad) { super(context); dotradius = rad; setLayoutParams(new LayoutParams(dotradius * 2, dotradius * 2)); drawNormal(); } /** * 繪製未通過時的狀態 */ public void drawNormal() { passed = false; Bitmap bm = Bitmap.createBitmap(dotradius * 2, dotradius * 2, Config.ARGB_8888); Paint paint = new Paint(); Canvas canvas = new Canvas(bm); paint.setAntiAlias(true); paint.setARGB(255, 156, 156, 156); paint.setStyle(Style.STROKE); paint.setStrokeWidth(5); canvas.drawCircle(dotradius, dotradius, dotradius - paint.getStrokeWidth(), paint); paint.setStrokeWidth(1); paint.setStyle(Style.FILL_AND_STROKE); canvas.drawCircle(dotradius, dotradius, 3, paint); setImageBitmap(bm); } /** * 繪製通過時的狀態 */ public void drawPassed() { passed = true; Bitmap bm = Bitmap.createBitmap(dotradius * 2, dotradius * 2, Config.ARGB_8888); Paint paint = new Paint(); Canvas canvas = new Canvas(bm); paint.setAntiAlias(true); paint.setARGB(255, 0, 0, 255); paint.setStyle(Style.STROKE); paint.setStrokeWidth(5); canvas.drawCircle(dotradius, dotradius, dotradius - paint.getStrokeWidth(), paint); paint.setStyle(Style.FILL_AND_STROKE); canvas.drawCircle(dotradius, dotradius, dotradius / 3, paint); setImageBitmap(bm); } public boolean getPassed() { return passed; } } }
佈局xml代碼,很簡單:數組
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <RelativeLayout android:id="@+id/rela" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageView android:id="@+id/view" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>