一. 前言 封装了手势解锁的基本操作,正常状态和选中状态的图片都可以替换,线条的颜色和宽度也可以改变,同时你可以设置同一个点能否选择多次。为了绘制的线条在点视图的下面,所以需要嵌套一个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)); } }