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

 更新时间:2022年06月28日 11:49:40   作者:冷薄荷  
这篇文章主要为大家详细介绍了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);
    }
}

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

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

相关文章

  • Android自定义View绘制彩色圆弧

    Android自定义View绘制彩色圆弧

    这篇文章主要为大家详细介绍了Android自定义View绘制彩色圆弧,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • android设计模式之单例模式详解

    android设计模式之单例模式详解

    这篇文章主要介绍了android设计模式中的单例模式详解,需要的朋友可以参考下
    2014-04-04
  • 从源码角度分析Android的消息机制

    从源码角度分析Android的消息机制

    这篇文章主要介绍了从源码角度分析Android的消息机制,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下
    2021-03-03
  • Android SeekBar实现滑动条效果

    Android SeekBar实现滑动条效果

    这篇文章主要为大家详细介绍了Android SeekBar实现滑动条效果,可以改变并显示当前进度的拖动,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • 分享五种Android常用布局方式

    分享五种Android常用布局方式

    Android布局是应用界面开发的重要一环,在Android中,共有五种布局方式,分别是:FrameLayout(框架布 局),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局),小编通过本文逐一给大家详解
    2015-11-11
  • Android从系统Gallery获取图片具体实现

    Android从系统Gallery获取图片具体实现

    这篇文章主要介绍了Android从系统Gallery获取图片具体实现,有需要的朋友可以参考一下
    2013-12-12
  • Retrofit2日志拦截器的使用

    Retrofit2日志拦截器的使用

    这篇文章主要介绍了Retrofit2日志拦截器的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11
  • ​​​​​​​Android H5通用容器架构设计详解

    ​​​​​​​Android H5通用容器架构设计详解

    这篇文章主要介绍了​​​​​​​Android H5通用容器架构设计详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • Android BroadcastReceiver实现网络状态实时监听

    Android BroadcastReceiver实现网络状态实时监听

    这篇文章主要为大家详细介绍了Android BroadcastReceiver实现网络状态实时监听,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • Android使用OkHttp请求自签名的https网站的示例

    Android使用OkHttp请求自签名的https网站的示例

    本篇文章主要介绍了Android使用OkHttp请求自签名的https网站的示例,非常具有实用价值,需要的朋友可以参考下、
    2017-09-09

最新评论