Android自定义控件实现九宫格解锁

关于九宫格解锁,我看了不少博客,但是都感觉很复杂,可能我的功夫还不到,所以很多东西我不了解,但是我还是打算写一个自己的九宫格。我相信我的九宫格大家都能很快的理解,当然如果需要实现更复杂的功能,需要大家自己接着往深了挖掘。

代码文件​​​​​​

NineGroupView:为九宫格空间组

ToggleView:九宫格中的子View,也就是我们看到的圆形按钮,我自己定义的ToggleView可能不好看,当然大家可以自己定义更加好看的ToggleView。

MarkBean:记录ToggleView的索引(ChildIndex)以及是否选中的状态

PositionUtils:工具类,包含规划九个ToggleView的中心点位置,判断当前触摸点是否属于ToggleView中等方法。

NineActivity:测试页面。

布局规划图

public class PositionUtils {     /**      * 判断触摸的点是否属于View中的一点      *      * @param point    触摸的点      * @param position 目标对象圆形坐标      * @param outR     目标对象的外半径      * @return      */     public static boolean IsIn(Point point, Point position, int outR) {         int touchX = point.x;         int touchY = point.y;         int cx = position.x;         int cy = position.y;         int distance = (int) Math.sqrt(Math.pow((touchX - cx), 2) + Math.pow((touchY - cy), 2));         if (distance <= outR) {             return true;         } else {             return false;         }     }     /**      * 规划 child 的中心位置      *      * @param width      * @param height      * @return      */     public static List<Point> getNinePoints(int width, int height) {         List<Point> points = new ArrayList<>();         for (int i = 1; i <= 3; i++) {             for (int j = 1; j <= 3; j++) {                 points.add(getPoint(width, height, 0.25f * j, 0.2f * i + 0.1f));             }         }         return points;     }     /**      * 获取      *      * @param width  父控件的宽      * @param height 父控件的高      * @param x      横轴方向比例      * @param y      纵轴方向的比例      * @return      */     private static Point getPoint(int width, int height, float x, float y) {         Point point = new Point();         point.x = (int) (width * x);         point.y = (int) (height * y);         return point;     } } public class ToggleView extends View {     private Paint inPaint;     private Paint outPaint;     private int outColor;     private int inColor;     private int outR;     private int inR;     private boolean isChecked;     public int getOutColor() {         return outColor;     }     public void setOutColor(int outColor) {         this.outColor = outColor;     }     public int getInColor() {         return inColor;     }     public void setInColor(int inColor) {         this.inColor = inColor;     }     public int getOutR() {         return outR;     }     public void setOutR(int outR) {         this.outR = outR;     }     public int getInR() {         return inR;     }     public void setInR(int inR) {         this.inR = inR;     }     public boolean isChecked() {         return isChecked;     }     public void setChecked(boolean checked) {         isChecked = checked;     }     public ToggleView(Context context) {         this(context, null);     }     public ToggleView(Context context, @Nullable AttributeSet attrs) {         this(context, attrs, 0);     }     public ToggleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         init(context, attrs, defStyleAttr);     }     /**      * 初始化      */     private void init(Context context, AttributeSet attrs, int defStyleAttr) {         TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ToggleView, defStyleAttr, 0);         int indexCount = array.getIndexCount();         for (int i = 0; i < indexCount; i++) {             int attr = array.getIndex(i);             switch (attr) {                 case R.styleable.ToggleView_InCircleR_T:                     inR = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(Dimension.DP, 10, getResources().getDisplayMetrics()));                     break;                 case R.styleable.ToggleView_OutCircleR_T:                     outR = array.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(Dimension.DP, 50, getResources().getDisplayMetrics()));                     break;                 case R.styleable.ToggleView_InCircleColor_T:                     inColor = array.getColor(attr, 0xff00ffff);                     break;                 case R.styleable.ToggleView_OutCircleColor_T:                     outColor = array.getColor(attr, 0xff888888);                     break;             }         }         inPaint = new Paint();         inPaint.setStyle(Paint.Style.FILL_AND_STROKE);         inPaint.setColor(inColor);         inPaint.setAntiAlias(true);         outPaint = new Paint();         outPaint.setAntiAlias(true);         outPaint.setStrokeWidth(5);         outPaint.setStyle(Paint.Style.STROKE);     }     @Override     protected void onDraw(Canvas canvas) {         super.onDraw(canvas);         int cx = getWidth() / 2;         int cy = getHeight() / 2;         outPaint.setStyle(Paint.Style.FILL);         outPaint.setColor(Color.WHITE);         canvas.drawCircle(cx, cy, outR, outPaint);         outPaint.setStyle(Paint.Style.STROKE);         outPaint.setColor(outColor);         canvas.drawCircle(cx, cy, outR, outPaint);         canvas.drawCircle(cx, cy, inR, inPaint);     } } public class NineGroupView extends ViewGroup {     private OnFinishListener mListener;     public interface OnFinishListener {         public void onFinish(List<Integer> positionSet);     }     public void setOnFinishListener(OnFinishListener listener) {         this.mListener = listener;     }     private Paint paint;     private Path path;     private TreeMap<Integer, Boolean> checkedMap;     private List<Integer> checkedINdexSet;   //用于记录被选中的序号排列。     private List<Point> positionList;     private List<Point> childSize = new ArrayList<>();     public NineGroupView(Context context) {         this(context, null);     }     public NineGroupView(Context context, AttributeSet attrs) {         this(context, attrs, 0);     }     public NineGroupView(Context context, AttributeSet attrs, int defStyleAttr) {         super(context, attrs, defStyleAttr);         init();     }     public void reset() {         for (int i = 0; i < 9; i++) {             checkedMap.put(i, false);         }         checkedINdexSet.clear();         IsDownIn = false;         IsUp = false;         path.reset();         prePoint = new Point(-1, -1);         currentPoint = new Point(-1, -1);         invalidate();     }     private void init() {         checkedMap = new TreeMap<>();         for (int i = 0; i < 9; i++) {             checkedMap.put(i, false);         }         checkedINdexSet = new ArrayList<>();         positionList = new ArrayList<>();         path = new Path();         paint = new Paint();         paint.setStrokeWidth(10);         paint.setAntiAlias(true);         paint.setColor(Color.RED);         paint.setStyle(Paint.Style.STROKE);         //如果该方法在此不调用的话,那么onDraw()方法将不被调用,那么就无法完成连接线的绘制         setWillNotDraw(false);     }     @Override     protected void onLayout(boolean b, int left, int top, int right, int bottom) {         int height = getMeasuredHeight();         int width = getMeasuredWidth();         positionList = PositionUtils.getNinePoints(width, height);         int childCount = getChildCount();         for (int i = 0; i < childCount; i++) {             View child = getChildAt(i);             Point size = childSize.get(i);             Point position = positionList.get(i);             int cLeft = position.x - size.x;             int cTop = position.y - size.y;             int cRight = position.x + size.x;             int cBottom = position.y + size.y;             child.layout(cLeft, cTop, cRight, cBottom);         }     }     @Override     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {         int childCount = getChildCount();         for (int i = 0; i < childCount; i++) {             View child = getChildAt(i);             measureChild(child, widthMeasureSpec, heightMeasureSpec);             Point point = new Point();             point.x = child.getMeasuredWidth();             point.y = child.getMeasuredHeight();             childSize.add(point);         }         setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);     }     private boolean IsDownIn = false;     private boolean IsUp = false;     private Point prePoint = new Point(-1, -1);     private Point currentPoint = new Point(-1, -1);     @Override     public boolean onTouchEvent(MotionEvent event) {         int action = event.getAction();         int currentX = (int) event.getX();         int currentY = (int) event.getY();         switch (action) {             case MotionEvent.ACTION_DOWN: {                 if (IsUp) {                     return true;                 }                 MarkBean bean = isInToggle(new Point(currentX, currentY));                 if (bean != null) {                     IsDownIn = true;                     prePoint = positionList.get(bean.getIndex());                     path.moveTo(prePoint.x, prePoint.y);                     invalidate();                 }             }             break;             case MotionEvent.ACTION_UP:                 IsUp = true;                 if (IsDownIn) {                     currentPoint = prePoint;                     IsDownIn = false;                     invalidate();                     if (mListener != null) {                         mListener.onFinish(checkedINdexSet);                         reset();                     }                 }                 break;             case MotionEvent.ACTION_MOVE: {                 if (IsDownIn) {                     if (!IsUp) {                         MarkBean bean = isInToggle(new Point(currentX, currentY));                         if (bean != null) {                             int index = bean.getIndex();                             currentPoint = positionList.get(index);                             path.lineTo(currentPoint.x, currentPoint.y);                             invalidate();                             prePoint = currentPoint;                         } else {                             currentPoint = new Point(currentX, currentY);                             invalidate();                         }                     }                 } else {                     if (!IsUp) {                         MarkBean bean = isInToggle(new Point(currentX, currentY));                         if (bean != null) {                             Point position = positionList.get(bean.getIndex());                             prePoint = position;                             path.moveTo(position.x, position.y);                             IsDownIn = true;                             invalidate();                         }                     }                 }             }             break;             case MotionEvent.ACTION_CANCEL:                 break;         }         return true;     }     private MarkBean isInToggle(Point point) {         MarkBean bean = new MarkBean();         int childCount = getChildCount();         for (int i = 0; i < childCount; i++) {             Point position = positionList.get(i);             ToggleView child = (ToggleView) getChildAt(i);             if (PositionUtils.IsIn(point, position, child.getOutR())) {                 if (!checkedMap.get(i)) {                     checkedMap.put(i, true);                     checkedINdexSet.add(i);                     bean.setIndex(i);                     bean.setCheck(true);                     return bean;                 }             }         }         return null;     }     @Override     protected void onDraw(Canvas canvas) {         canvas.drawPath(path, paint);         if (prePoint.x != -1 && prePoint.y != -1 && currentPoint.x != -1 && currentPoint.y != -1) {             canvas.drawLine(prePoint.x, prePoint.y, currentPoint.x, currentPoint.y, paint);         }         super.onDraw(canvas);     } }

代码总是最直接的引导,我看博客最喜欢的是研究代码,当然如果代码中有一些讲解就更好了。

推荐阅读