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

来自:网络
时间:2022-08-07
阅读:

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

代码文件​​​​​​

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

NineGroupView:为九宫格空间组

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

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

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

NineActivity:测试页面。

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

布局规划图

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);
    }
}

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

返回顶部
顶部