Android——自定义View实现9宫格解锁
Android——自定义View实现9宫格解锁
自定义View
- 1.实现一个子类继承View
- 2.覆盖onDrow()函数,渲染图像
- 3.覆盖onTouchEvent()函数
- 4.监听按下、移动,松开手指的动作
- 5.重新在onDrow()中渲染对应的的图像
这是一个仿京东金融的一个九宫格解锁,最上面的日期显示使用的Time()获取到当前的时间,我们得到日期对其赋值就好了。
九宫格解锁有两个模式:CREATE_MODE CHECK_MODE两种模式,一种是用来设置密码的模式,一种是输入密码的模式。验证登录密码会有三次机会,三次都输入错误会退出项目,忘记密码了也可以进行重新设置密码,非常的方便。重新设置手势密码这块没有进行验证,有兴趣的朋友可以用推送实现一个短信验证的效果。
添加依赖:
compile 'com.facebook.fresco:fresco:0.11.0'
自定义一个view:
public class UnlockView extends View{ public static final int CIRCLE_NORMAL = 1;//normal state of circle public static final int CIRCLE_SELECTED = 2;//selected state of circle public static final int CIRCLE_ERROR = 3;//error state of circle public static final int CREATE_MODE = 22;//this mode is used for creating gesture public static final int CHECK_MODE = 33;//this mode is used for checking gesture @UnlockMode private int mode;//define the mode private int width;//the width of screen,valued in onMeasure private int height;//the height of screen,valued in onMeasure private int rootX;//root position of the line which can move private int rootY;//root position of the line which can move private Context ctx; private ArrayList<Circle> circleList = new ArrayList<>();//store the circles on screen private ArrayList<Circle> pathCircleList = new ArrayList<>();//store the selected circles private Bitmap circletBmp;//used for drawing circles private Canvas mCanvas; private Paint cirNorPaint;//paint of normal state circles private Paint cirSelPaint;//paint of selected state circles private Paint smallCirSelPaint;//paint of selected state small circles private Paint cirErrPaint;//paint of error state circles private Paint smallcirErrPaint;//paint of error state small circles private Paint pathPaint;//paint of the lines private Path mPath; private Path tempPath; private int pathWidth = 3;//width of the paint of path private int normalR = 15;//radius of small circles; private int selectR = 30;//radius of big circles; private int strokeWidth = 2;//width of big circles; private int normalColor = Color.parseColor("#D5DBE8");//defalt color of normal state private int selectColor = Color.parseColor("#508CEE");//defalt color of selected state private int errorColor = Color.parseColor("#FF3153");//defalt color of error state private boolean isUnlocking; private boolean isShowError; private boolean hasNewCircles; private ArrayList<Integer> passList = new ArrayList<>(); private OnUnlockListener listener;//the listener of unlock private CreateGestureListener createListener;//the listener of creating gesture /** * used for refresh the canvas after MotionEvent.ACTION_UP */ private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { resetAll(); invalidate(); return true; } }); public UnlockView(Context context) { super(context); this.ctx = context; strokeWidth = dip2px(ctx, strokeWidth); normalR = dip2px(ctx, normalR); selectR = dip2px(ctx, selectR); pathWidth = dip2px(ctx, pathWidth); } public UnlockView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); this.ctx = context; strokeWidth = dip2px(ctx, strokeWidth); normalR = dip2px(ctx, normalR); selectR = dip2px(ctx, selectR); pathWidth = dip2px(ctx, pathWidth); } public UnlockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.ctx = context; strokeWidth = dip2px(ctx, strokeWidth); normalR = dip2px(ctx, normalR); selectR = dip2px(ctx, selectR); pathWidth = dip2px(ctx, pathWidth); } /** * reset all states */ private void resetAll() { isShowError = false; isUnlocking = false; mPath.reset(); tempPath.reset(); pathCircleList.clear(); passList.clear(); for (Circle circle : circleList) { circle.setState(CIRCLE_NORMAL); } pathPaint.setColor(selectColor); cirSelPaint.setColor(selectColor); smallCirSelPaint.setColor(selectColor); clearCanvas(); } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(circletBmp, 0, 0, null); for (int i = 0; i < circleList.size(); i++) { drawCircles(circleList.get(i)); } canvas.drawPath(mPath, pathPaint); } @Override public boolean onTouchEvent(MotionEvent event) { if (isShowError) return true; int curX = (int) event.getX(); int curY = (int) event.getY(); Circle circle = getOuterCircle(curX, curY); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: this.resetAll(); if (circle != null) { rootX = circle.getX(); rootY = circle.getY(); circle.setState(CIRCLE_SELECTED); pathCircleList.add(circle); tempPath.moveTo(rootX, rootY); addItem(circle.getPosition() + 1); isUnlocking = true; } break; case MotionEvent.ACTION_MOVE: if (isUnlocking) { mPath.reset(); mPath.addPath(tempPath); mPath.moveTo(rootX, rootY); mPath.lineTo(curX, curY); handleMove(circle); } break; case MotionEvent.ACTION_UP: isUnlocking = false; if (pathCircleList.size() > 0) { mPath.reset(); mPath.addPath(tempPath); StringBuilder sb = new StringBuilder(); for (Integer num : passList) { sb.append(num); } if (this.mode == CREATE_MODE) { if(createListener!=null){ createListener.onGestureCreated(sb.toString()); }else{ Log.e("UnLockView","Please set CreateGestureListener first!"); } } else if(this.mode == CHECK_MODE){ if(listener!=null){ if (listener.isUnlockSuccess(sb.toString())) { listener.onSuccess(); } else { listener.onFailure(); for (Circle circle1 : pathCircleList) { circle1.setState(CIRCLE_ERROR); } pathPaint.setColor(errorColor); } }else{ Log.e("UnLockView","Please set OnUnlockListener first!"); } }else{ try { throw new Exception("Please set mode first!"); } catch (Exception e) { e.printStackTrace(); } } isShowError = true; handler.postDelayed(new Runnable() { @Override public void run() { handler.sendEmptyMessage(0); } }, 1000); } break; } invalidate(); return true; } private synchronized void handleMove(Circle c) { if (c != null&&!(c.getState()==CIRCLE_SELECTED)) { c.setState(CIRCLE_SELECTED); pathCircleList.add(c); rootX = c.getX(); rootY = c.getY(); tempPath.lineTo(rootX, rootY); addItem(c.getPosition() + 1); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = getMeasuredWidth(); height = getMeasuredHeight(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mode != CHECK_MODE || mode != CREATE_MODE) { Log.e("UnlockView", "Please set mode first!"); } //init all path/paint mPath = new Path(); tempPath = new Path(); pathPaint = new Paint(); pathPaint.setColor(selectColor); pathPaint.setDither(true); pathPaint.setAntiAlias(true); pathPaint.setStyle(Paint.Style.STROKE); pathPaint.setStrokeCap(Paint.Cap.ROUND); pathPaint.setStrokeJoin(Paint.Join.ROUND); pathPaint.setStrokeWidth(pathWidth); //普通状态小圆画笔 circletBmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(circletBmp); cirNorPaint = new Paint(); cirNorPaint.setAntiAlias(true); cirNorPaint.setDither(true); cirNorPaint.setColor(normalColor); //选中状态大圆画笔 cirSelPaint = new Paint(); cirSelPaint.setAntiAlias(true); cirSelPaint.setDither(true); cirSelPaint.setStyle(Paint.Style.STROKE); cirSelPaint.setStrokeWidth(strokeWidth); cirSelPaint.setColor(selectColor); //选中状态小圆画笔 smallCirSelPaint = new Paint(); smallCirSelPaint.setAntiAlias(true); smallCirSelPaint.setDither(true); smallCirSelPaint.setColor(selectColor); //出错状态大圆画笔 cirErrPaint = new Paint(); cirErrPaint.setAntiAlias(true); cirErrPaint.setDither(true); cirErrPaint.setStyle(Paint.Style.STROKE); cirErrPaint.setStrokeWidth(strokeWidth); cirErrPaint.setColor(errorColor); //出错状态小圆画笔 smallcirErrPaint = new Paint(); smallcirErrPaint.setAntiAlias(true); smallcirErrPaint.setDither(true); smallcirErrPaint.setColor(errorColor); //init all circles int hor = width / 6; int ver = height / 6; if(!hasNewCircles){ for (int i = 0; i < 9; i++) { int tempX = (i % 3 + 1) * 2 * hor - hor; int tempY = (i / 3 + 1) * 2 * ver - ver; Circle circle = new Circle(i, tempX, tempY, CIRCLE_NORMAL); circleList.add(circle); } } hasNewCircles=true; } /** * called in onDraw for drawing all circles * * @param circle */ private void drawCircles(Circle circle) { switch (circle.getState()) { case CIRCLE_NORMAL: mCanvas.drawCircle(circle.getX(), circle.getY(), normalR, cirNorPaint); break; case CIRCLE_SELECTED: mCanvas.drawCircle(circle.getX(), circle.getY(), selectR, cirSelPaint); mCanvas.drawCircle(circle.getX(), circle.getY(), normalR, smallCirSelPaint); break; case CIRCLE_ERROR: mCanvas.drawCircle(circle.getX(), circle.getY(), selectR, cirErrPaint); mCanvas.drawCircle(circle.getX(), circle.getY(), normalR, smallcirErrPaint); break; } } /** * clear canvas */ private void clearCanvas() { Paint p = new Paint(); p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mCanvas.drawPaint(p); p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); mPath.reset(); tempPath.reset(); } /** * J U S T A T O O L ! * * @param context Context * @param dipValue value of dp * @return */ public static int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } /** * check whether the point is in a circle * * @param x * @param y * @return */ @Nullable private Circle getOuterCircle(int x, int y) { for (int i = 0; i < circleList.size(); i++) { Circle circle = circleList.get(i); if ((x - circle.getX()) * (x - circle.getX()) + (y - circle.getY()) * (y - circle.getY()) <= normalR * normalR) { if (circle.getState() != CIRCLE_SELECTED) { return circle; } } } return null; } /** * check whether the password list contains the number * * @param num * @return */ private boolean arrContainsInt(int num) { for (Integer value : passList) { if (num == value) { return true; } } return false; } /** * put the num into password list * * @param num */ private void addItem(Integer num) { if (!arrContainsInt(num)) { passList.add(num); } } /** * Create Mode Listener */ interface CreateGestureListener { void onGestureCreated(String result); } public void setGestureListener(CreateGestureListener listener) { this.createListener = listener; } /** * Check Mode Listener */ interface OnUnlockListener { boolean isUnlockSuccess(String result); void onSuccess(); void onFailure(); } public void setOnUnlockListener(OnUnlockListener listener) { this.listener = listener; } public void setPathWidth(int pathWidth) { this.pathWidth = pathWidth; } public void setNormalR(int normalR) { this.normalR = normalR; } public void setSelectR(int selectR) { this.selectR = selectR; } public void setNormalColor(int normalColor) { this.normalColor = normalColor; } public void setSelectColor(int selectColor) { this.selectColor = selectColor; } public void setErrorColor(int errorColor) { this.errorColor = errorColor; } public void setMode(@UnlockMode int mode) { this.mode = mode; } class Circle { /** * position of the circle */ private int position; private int x; private int y; /** * the state of circle */ private int state; public Circle() { } public Circle(int position, int x, int y, int state) { this.position = position; this.x = x; this.y = y; this.state = state; } public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getState() { return state; } public void setState(int state) { this.state = state; } } /** * It's an annotation */ @IntDef({CREATE_MODE, CHECK_MODE}) @interface UnlockMode { } }
main_activity.xml:
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="08" android:textSize="40sp" android:textStyle="bold" android:layout_marginTop="20dp" android:layout_marginLeft="20dp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Dec" android:textSize="17sp" android:textStyle="bold" android:layout_toRightOf="@+id/time" android:layout_marginTop="40dp" android:layout_marginLeft="5dp" /> </RelativeLayout> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="未来的事情做好计划不去担心" android:textSize="20sp" android:layout_marginLeft="20dp" /> <com.facebook.drawee.view.SimpleDraweeView android:id="@+id/img" android:layout_width="100dp" android:layout_height="100dp" fresco:roundAsCircle="true" android:layout_marginTop="30dp" android:layout_marginBottom="30dp" android:layout_gravity="center_horizontal" fresco:placeholderImage="@mipmap/ic_launcher"/> <test.bwei.com.handler.UnlockView android:id="@+id/unlock" android:layout_width="400dp" android:layout_height="400dp" android:layout_gravity="center_horizontal" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="120dp" android:layout_gravity="center_horizontal" > <TextView android:id="@+id/forget" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="忘记手势密码" android:textSize="17sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="|" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:textSize="17sp" /> <TextView android:id="@+id/type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="请设置您的手势密码!" android:textColor="#f00" android:textSize="17sp" /> </LinearLayout> </LinearLayout>
main方法:
public class MainActivity extends AppCompatActivity { int count = 0; private UnlockView mUnlockView; private String pwd; private SharedPreferences sharedPreferences; private SharedPreferences.Editor edit; private SimpleDraweeView img; private TextView time; private TextView forget; private TextView type; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); forget = (TextView) findViewById(R.id.forget); type = (TextView) findViewById(R.id.type); /* ImmersionUtils immersionUtils = new ImmersionUtils(); immersionUtils.setImmersion(getWindow(), getSupportActionBar());*/ time = (TextView) findViewById(R.id.time); Time t=new Time(); // or Time t=new Time("GMT+8"); 加上Time Zone资料 t.setToNow(); // 取得系统时间。 int year = t.year; int month = t.month; int date = t.monthDay; int hour = t.hour; time.setText(date+""); img = (SimpleDraweeView) findViewById(R.id.img); mUnlockView = (UnlockView) findViewById(R.id.unlock); sharedPreferences = getSharedPreferences("data", Context.MODE_PRIVATE); if (sharedPreferences.getInt("flage",0)==1){ img.setImageURI(sharedPreferences.getString("img", "img")); }else { img.setImageResource(R.mipmap.ic_launcher); } forget.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { edit = sharedPreferences.edit(); edit.putBoolean("key",false); edit.commit(); onResume(); count=0; } }); } @Override protected void onResume() { super.onResume(); boolean key = sharedPreferences.getBoolean("key", false); pwd = sharedPreferences.getString("pwd", "pwd"); if (key) { mUnlockView.setMode(UnlockView.CHECK_MODE); type.setText("请登录你的密码"); mUnlockView.setOnUnlockListener(new UnlockView.OnUnlockListener() { @Override public boolean isUnlockSuccess(String result) { if (result.equals(pwd)) { return true; } else { return false; } } @Override public void onSuccess() { Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(MainActivity.this, MainActivity.class); startActivity(intent); finish(); } @Override public void onFailure() { count++; if (count == 3) { finish(); } else { Toast.makeText(MainActivity.this, "密码错误,还有" + (3 - count) + "次机会", Toast.LENGTH_SHORT).show(); } } }); } else { mUnlockView.setMode(UnlockView.CREATE_MODE); type.setText("请设置您的手势密码!"); mUnlockView.setGestureListener(new UnlockView.CreateGestureListener() { @Override public void onGestureCreated (String result){ Log.i("zzz", "onGestureCreated: " + result); pwd = result; edit = sharedPreferences.edit(); edit.putBoolean("key",true); edit.putString("pwd",pwd); edit.commit(); Toast.makeText(MainActivity.this, "密码设置成功!", Toast.LENGTH_SHORT).show(); mUnlockView.setMode(UnlockView.CHECK_MODE); onResume(); } }); } } }在Application初始化:
public class App extends Application{ @Override public void onCreate() { super.onCreate(); Fresco.initialize(this); } }
在manifest Application里注册:
android:name=".App"