一. 前言 封装了手势解锁的基本操作,正常状态和选中状态的图片都可以替换,线条的颜色和宽度也可以改变,同时你可以设置同一个点能否选择多次。为了绘制的线条在点视图的下面,所以需要嵌套一个RelativeLayout,XLPictureUnlcok在嵌套的RelativeLayout中的宽高都是match_parent。
Github地址:XLPictureUnlock 
二. 自定义View 1. 自定义用到的属性 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 private  int  normal_image_id = R.drawable.normal;private  int  select_image_id = R.drawable.selected;private  int  dot_size = 45 ;private  int  line_color = Color.parseColor("#99CCFF" );private  int  line_width = 10 ;private  boolean  can_select_again = false ;private  Point start_point;private  Point end_point;private  Paint paint;private  List<ImageView> dots;private  List<ImageView> light_dots;private  List<Path> paths;private  CallBackPasswordListener listener;
2. 在values文件夹下自定义属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="utf-8"?> <resources >     <declare-styleable  name ="XLPictureUnlock" >                   <attr  name ="normal_image_id"  format ="reference" />                   <attr  name ="select_image_id"  format ="reference" />                   <attr  name ="dot_size"  format ="integer" />                   <attr  name ="line_color"  format ="color" />                   <attr  name ="line_width"  format ="integer" />                   <attr  name ="can_select_again"  format ="boolean" />      </declare-styleable >  </resources > 
3. 创建构造方法 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 public  XLPictureUnlock (Context context)      super (context);          init(); } public  XLPictureUnlock (Context context, AttributeSet attrs)      super (context, attrs);          if  (attrs != null ){                  TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XLPictureUnlock);                  normal_image_id = typedArray.getResourceId(R.styleable.XLPictureUnlock_normal_image_id,normal_image_id);         select_image_id = typedArray.getResourceId(R.styleable.XLPictureUnlock_select_image_id,select_image_id);         dot_size = PxUtil.dpToPx(typedArray.getInteger(R.styleable.XLPictureUnlock_dot_size,dot_size),getContext());         line_color = typedArray.getColor(R.styleable.XLPictureUnlock_line_color,line_color);         line_width = PxUtil.dpToPx(typedArray.getInteger(R.styleable.XLPictureUnlock_line_width,line_width),getContext());         can_select_again = typedArray.getBoolean(R.styleable.XLPictureUnlock_can_select_again,can_select_again);                  typedArray.recycle();     }          init(); } private  void  init ()           dots = new  ArrayList<>();     light_dots = new  ArrayList<>();     paths = new  ArrayList<>();          paint = new  Paint();     paint.setColor(line_color);     paint.setStrokeWidth(line_width);     paint.setStrokeCap(Paint.Cap.ROUND);     paint.setStrokeJoin(Paint.Join.ROUND);     paint.setStyle(Paint.Style.STROKE);     paint.setAntiAlias(true );          setLayerType(View.LAYER_TYPE_SOFTWARE, null ); } 
4. 绘制点 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 public  void  addDotView (final  RelativeLayout layout)     this .post(new  Runnable() {         @Override          public  void  run ()                            int  width = getWidth();             int  height = getHeight();                          int  hor_padding = (width - dot_size * 3 ) / 4 ;             int  ver_padding = (height - dot_size * 3 ) / 4 ;                          for  (int  i = 0 ; i < 9 ; i++) {                                  int  col = i % 3 ;                                  int  row = i / 3 ;                                  @SuppressLint("DrawAllocation")  ImageView dot = new  ImageView(getContext());                                  dot.setImageResource(normal_image_id);                                  dot.setScaleType(ImageView.ScaleType.FIT_XY);                                  LayoutParams layoutParams = new  LayoutParams(                         dot_size,                         dot_size                 );                 layoutParams.leftMargin = hor_padding + (hor_padding + dot_size) * col;                 layoutParams.topMargin = ver_padding + (ver_padding + dot_size) * row;                                  layout.addView(dot,layoutParams);                                  dot.setId(i+1 );                                  dots.add(dot);             }         }     }); } 
5. 触摸事件以及回调 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 @SuppressLint("ClickableViewAccessibility") @Override public  boolean  onTouchEvent (MotionEvent event)           float  x = event.getX();     float  y = event.getY();          ImageView dot;          switch  (event.getAction()){         case  MotionEvent.ACTION_DOWN:                          dot = isDotsContainPoint(x, y);                          if  (dot != null ){                 dot.setImageResource(select_image_id);                 light_dots.add(dot);                 start_point = new  Point(                         (int )(dot.getX()+dot.getPivotX()),                         (int )(dot.getY()+dot.getPivotY())                 );             }             break ;         case  MotionEvent.ACTION_MOVE:                          dot = isDotsContainPoint(x, y);                          if  (dot != null ) {                                  if  (!isDotChangeLight(dot) || can_select_again) {                                          if  (!isDotChangeLight(dot)){                         dot.setImageResource(select_image_id);                         light_dots.add(dot);                     }else  {                                                  if  (light_dots.get(light_dots.size()-1 ).getId() != dot.getId()){                             light_dots.add(dot);                         }                     }                                          if  (start_point != null ){                                                                           Path path = new  Path();                         path.moveTo(start_point.x,start_point.y);                         path.lineTo(                                 (dot.getX()+dot.getPivotX()),                                 (dot.getY()+dot.getPivotY())                         );                         paths.add(path);                                                  invalidate();                                                  start_point = new  Point(                                 (int )(dot.getX()+dot.getPivotX()),                                 (int )(dot.getY()+dot.getPivotY())                         );                         end_point = start_point;                                                  invalidate();                     }else  {                                                  start_point = new  Point(                                 (int )(dot.getX()+dot.getPivotX()),                                 (int )(dot.getY()+dot.getPivotY())                         );                     }                 }             }else  {                                  end_point = new  Point((int )x,(int )y);                                  invalidate();             }             break ;         case  MotionEvent.ACTION_UP:             end_point = start_point;                          invalidate();                          for  (ImageView light_dot : light_dots) {                 light_dot.setImageResource(normal_image_id);             }                          if  (listener != null ){                 StringBuilder stringBuilder = new  StringBuilder();                 for  (int  i = 0 ; i < light_dots.size(); i++) {                     stringBuilder.append(light_dots.get(i).getId());                 }                 if  (!stringBuilder.toString().equals("" )){                     listener.picturePassword(stringBuilder.toString());                 }             }                          light_dots.clear();                          paths.clear();                          start_point = null ;             end_point = null ;            break ;     }     return  true ; } public  interface  CallBackPasswordListener      void  picturePassword (String password)  } 
6. 绘制过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override protected  void  dispatchDraw (Canvas canvas)      super .dispatchDraw(canvas);          if  (paths.size() > 0 ){         for  (Path path : paths) {             canvas.drawPath(path,paint);         }     }          if  (start_point != end_point && start_point != null  && end_point != null ){         canvas.drawLine(                 start_point.x,start_point.y,                 end_point.x,end_point.y,paint         );     } } 
7. 辅助判断函数 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 private  boolean  isDotChangeLight (ImageView dot)     for  (ImageView light_dot : light_dots) {         if  (light_dot.getId() == dot.getId()){             return  true ;         }     }     return  false ; } private  ImageView isDotsContainPoint (float  x, float  y)          for  (ImageView dot : dots) {                  RectF rectF = new  RectF(dot.getLeft(), dot.getTop(), dot.getRight(), dot.getBottom());                  if  (rectF.contains(x,y)){             return  dot;         }     }     return  null ; } 
三. 具体的使用 首先需要添加依赖,添加依赖之后,在布局文件中这样调用。
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 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout  xmlns:android ="http://schemas.android.com/apk/res/android"      xmlns:app ="http://schemas.android.com/apk/res-auto"      xmlns:tools ="http://schemas.android.com/tools"      android:layout_width ="match_parent"      android:layout_height ="match_parent"      tools:context =".MainActivity"      android:background ="@drawable/bg" >     <TextView         android:id="@+id/text"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:text="请输入密码"         android:textSize="30sp"         android:textColor="#ffffff"         android:textAlignment="center"         android:layout_marginTop="80dp"         />     <Button         android:id="@+id/btn"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:background="@null"         android:text="忘记密码?"         android:textColor="#ff99cc"         android:layout_centerHorizontal="true"         android:layout_below="@id/text"         />     <RelativeLayout         android:id="@+id/layout"         android:layout_width="match_parent"         android:layout_height="match_parent"         android:layout_below="@+id/text"         >         <swu.xl.pictureunlock_draw.XLPictureUnlock             android:id="@+id/unlock"             android:layout_width="match_parent"             android:layout_height="match_parent"             app:can_select_again="true"             app:dot_size="65"             app:line_width="8"             />     </RelativeLayout >  </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 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 public  class  MainActivity  extends  AppCompatActivity           private  final  String SHARE_NAME = "PictureUnlockSelf" ;     private  final  String PASSWORD_KEY = "password" ;          private  String firPassword = "" ;     private  String password = "" ;     @Override      protected  void  onCreate (Bundle savedInstanceState)           super .onCreate(savedInstanceState);         setContentView(R.layout.activity_main);                  final  SharedPreferences sharedPreferences = getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE);                  @SuppressLint("CommitPrefEdits")  final  SharedPreferences.Editor edit = sharedPreferences.edit();                  final  TextView text = findViewById(R.id.text);         password = sharedPreferences.getString(PASSWORD_KEY, "" );         if  (password.length() == 0 ){                          text.setText("请设置密码" );         }else  {                          text.setText("请输入密码" );         }                  Button btn = findViewById(R.id.btn);         btn.setOnClickListener(new  View.OnClickListener() {             @Override              public  void  onClick (View v)                                    edit.putString(PASSWORD_KEY, "" );                                  edit.apply();                 firPassword = "" ;                 password = "" ;                                  text.setText("请重新设置密码" );             }         });                  XLPictureUnlock pictureUnlock = findViewById(R.id.unlock);                  RelativeLayout layout = findViewById(R.id.layout);                  pictureUnlock.addDotView(layout);                  pictureUnlock.setCallBackPasswordListener(new  XLPictureUnlock.CallBackPasswordListener() {             @Override              public  void  picturePassword (String pwd)                   if  (password.length() == 0 ){                     if  (firPassword.length() == 0 ){                                                  firPassword = pwd;                                                  text.setText("请再次输入密码以确认" );                     }else  {                                                  if  (firPassword.equals(pwd)){                                                                                       text.setText("密码设置成功" );                                                          edit.putString(PASSWORD_KEY, pwd);                                                          edit.apply();                                                          firPassword = "" ;                             jump();                         }else  {                                                          text.setText("两次密码不一致,请重新输入" );                         }                     }                 }else  {                     if  (password.equals(pwd)){                                                  text.setText("密码正确" );                         jump();                     }else  {                                                  text.setText("密码错误" );                     }                 }             }         });     }          private  void  jump ()          startActivity(new  Intent(this ,SecondActivity.class));     } }