Android自定义控件之九宫格

时间:2022-06-01
本文章向大家介绍Android自定义控件之九宫格,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

在网上找了好多九宫格控件,但是都不尽人意,所以自己写了一个,写个博客记录一下

LockView.java

package com.tianjs.tianjinsuop2p.widgets;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

import java.util.ArrayList;
import java.util.List;

/**
 * 九宫格控件
 * Created by xiaolei on 2017/3/16.
 */

public class LockView extends View
{
    private int pointCount = 3;
    private Cell cells[][];
    private Paint mPaint;
    private int ScreenWidth;//屏幕宽度
    private int ScreenHeight;//屏幕高度
    private int RADIUS;//半径
    private int OFFSET;//点与点之间的距离
    private int startX, startY;
    private int lastTouchX = 0;
    private int lastTouchY = 0;
    private List<Cell> selectCells = new ArrayList<>();
    
    private int normalCricleColor = Color.WHITE;//正常的宫格的颜色
    private int selectCricleColor = Color.RED;//选中的宫格的颜色
    
    private int normalCriclePointColor = Color.WHITE;//正常的宫格内点的颜色
    private int selectCriclePointColor = Color.RED;//选中的宫格内点的颜色
    
    private int lineColor = Color.RED;//线的颜色
    private int topLineColor = Color.RED;//线的顶端的颜色
    private int circleStrokeWidth = 5;//圆的环的宽度
    private int lineStrokeWidth = 4;//线的宽度
    
    private boolean showInLastCircle = true;//画线的时候,是否在最后的那个圈内显示
    private int minSeleted = 4;//最小选中数量
    private OnSelectListener onSelectListener;
    
    public LockView(Context context)
    {
        this(context, null);
    }

    public LockView(Context context, AttributeSet attrs)
    {
        this(context, attrs, 0);
    }

    public LockView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.LockView);

        pointCount = array.getInt(R.styleable.LockView_pointCount,3);
        showInLastCircle = array.getBoolean(R.styleable.LockView_showInLastCircle,true);
        lineStrokeWidth = array.getInt(R.styleable.LockView_lineStrokeWidth,4);
        circleStrokeWidth = array.getInt(R.styleable.LockView_circleStrokeWidth,4);
        
        topLineColor = array.getColor(R.styleable.LockView_topLineColor,Color.RED);
        lineColor = array.getColor(R.styleable.LockView_lineColor,Color.RED);
        selectCriclePointColor = array.getColor(R.styleable.LockView_selectCriclePointColor,Color.RED);
        
        normalCriclePointColor = array.getColor(R.styleable.LockView_normalCriclePointColor,Color.WHITE);
        normalCricleColor = array.getColor(R.styleable.LockView_normalCricleColor,Color.WHITE);
        selectCricleColor = array.getColor(R.styleable.LockView_selectCricleColor,Color.RED);
        
        array.recycle();
        init(context);
    }

    private void init(Context context)
    {
        cells = new Cell[pointCount][pointCount];
        mPaint = new Paint();
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.GRAY);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStyle(Paint.Style.STROKE);
        WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        manager.getDefaultDisplay().getMetrics(dm);
        ScreenWidth = dm.widthPixels;
        ScreenHeight = dm.heightPixels;
        Log.e("LockView", "ScreenWidth:" + ScreenWidth + " ScreenHeight:" + ScreenHeight);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // 父容器传过来的宽度方向上的模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        // 父容器传过来的高度方向上的模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        // 父容器传过来的宽度的值
        int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft()
                - getPaddingRight();
        // 父容器传过来的高度的值
        int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingLeft()
                - getPaddingRight();
        Log.e("LockView","onMeasure(width:"+width+",height:"+height+")");

        //计算半径
        RADIUS = Math.min(width, height) / ((pointCount * 2) + (pointCount + 1));
        OFFSET = RADIUS;
        //开始计算左上角的X,Y轴
        if (ScreenWidth < ScreenHeight)//竖屏
        {
            Log.d("LockView", "竖屏");
            startX = RADIUS * 2;
            startY = RADIUS * 2;
        } else//横屏
        {
            Log.d("LockView", "横屏");
            startX = (width - height)/2 + (RADIUS * 2);
            startY = RADIUS * 2;
        }
        initCells();
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    private void initCells()//计算每个点的位置
    {
        for (int i = 0; i < cells.length; i++)
        {
            for (int j = 0; j < cells[i].length; j++)
            {
                if(cells[i][j] == null)
                {
                    cells[i][j] = new Cell();
                }
                cells[i][j].x = (((j + 1) * (3 * RADIUS)) - RADIUS) + (startX - RADIUS * 2);
                cells[i][j].y = (((i + 1) * (3 * RADIUS)) - RADIUS);
            }
        }
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
            {
                selectCells.clear();
                Cell selectCell = inWhichCircle((int) event.getX(), (int) event.getY());
                if(selectCell != null && !selectCells.contains(selectCell))
                {
                    selectCells.add(selectCell);
                    this.postInvalidate();
                }
                break;
            }
            case MotionEvent.ACTION_MOVE:
            {
                Cell selectCell = inWhichCircle((int) event.getX(), (int) event.getY());
                if(selectCell != null && !selectCells.contains(selectCell))
                {
                    selectCells.add(selectCell);
                }
                lastTouchX = (int) event.getX();
                lastTouchY = (int) event.getY();
                this.postInvalidate();
                break;
            }
            case MotionEvent.ACTION_UP:
            {
                lastTouchX = 0;
                lastTouchY = 0;
                if(selectCells.size() < minSeleted)//抬起手的时候,判断选中的点的数量是否小小于最小选中数
                {
                    selectCells.clear();
                }else if(onSelectListener != null)
                {
                    List<Cell> list = new ArrayList<>();
                    list.addAll(selectCells);
                    onSelectListener.onSelected(list);
                }
                this.postInvalidate();
                break;   
            }
        }
        return true;
    }

    /**
     * 判断某个触摸点,是否在某个宫格内
     * @param x
     * @param y
     * @return
     */
    private Cell inWhichCircle(int x, int y)
    {
        Cell result = null;
        for (int i = 0; i < cells.length; i++)
        {
            for (int j = 0; j < cells[i].length; j++)
            {
                //选择画笔&&画圆  
                Cell cell = cells[i][j];
                if (isInCell(cell,x,y))
                {
                    return cell;
                }
            }
        }
        return result;
    }

    /**
     * 这个点是否在这个宫格里
     * @param cell
     * @param x
     * @param y
     * @return
     */
    private boolean isInCell(Cell cell,int x,int y)
    {
        return (Math.abs(x - cell.x) < RADIUS) && Math.abs(y - cell.y) < RADIUS;
    }
    
    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        drawCell(canvas);
        drawLine(canvas);
    }

    /**
     * 画线
     * @param canvas
     */
    private void drawLine(Canvas canvas)
    {
        mPaint.setColor(lineColor);
        mPaint.setStrokeWidth(lineStrokeWidth);
        Cell lastCell = null;
        for (Cell cell:selectCells)
        {
            if(lastCell != null)
            {
                canvas.drawLine(cell.x, cell.y, lastCell.x, lastCell.y, mPaint);
            }
            lastCell = cell;
        }
        if(showInLastCircle)
        {
            if((lastTouchX != 0 || lastTouchY != 0)
                    && lastCell != null)
            {
                canvas.drawLine(lastCell.x, lastCell.y,lastTouchX,lastTouchY, mPaint);
                mPaint.setColor(topLineColor);
                //线的顶端,画一个圆角的视觉效果,我喜欢
                canvas.drawCircle(lastTouchX, lastTouchY,0.8f, mPaint);
            }
        }else 
        {
            if((lastTouchX != 0 || lastTouchY != 0)
                    && lastCell != null
                    && !isInCell(lastCell,lastTouchX,lastTouchY))
            {
                canvas.drawLine(lastCell.x, lastCell.y,lastTouchX,lastTouchY, mPaint);
                mPaint.setColor(topLineColor);
                //线的顶端,画一个圆角的视觉效果,我喜欢
                canvas.drawCircle(lastTouchX, lastTouchY,0.8f, mPaint);
            }
        }
        
    }
    
    /**
     * 画出圆
     * @param canvas
     */
    private void drawCell(Canvas canvas)
    {
        for (int i = 0; i < cells.length; i++)
        {
            for (int j = 0; j < cells[i].length; j++)
            {
                //选择画笔&&画圆  
                Cell cell = cells[i][j];
                if (selectCells.contains(cell))
                {
                    mPaint.setColor(selectCricleColor);
                    mPaint.setStrokeWidth(circleStrokeWidth);
                    canvas.drawCircle(cell.x, cell.y, RADIUS, mPaint);
                    mPaint.setColor(selectCriclePointColor);
                    canvas.drawCircle(cell.x, cell.y, circleStrokeWidth/2, mPaint);
                } else
                {
                    mPaint.setColor(normalCricleColor);
                    mPaint.setStrokeWidth(circleStrokeWidth);
                    canvas.drawCircle(cell.x, cell.y, RADIUS, mPaint);
                    mPaint.setColor(normalCriclePointColor);
                    canvas.drawCircle(cell.x, cell.y, circleStrokeWidth/2, mPaint);
                }
                
            }
        }
    }

    public static class Cell
    {
        public int x;
        public int y;
    }

    /**
     * 设置宫格数量 X * X
     * @param pointCount
     */
    public void setPointCount(int pointCount)
    {
        this.pointCount = pointCount;
    }

    /**
     * 设置普通宫格颜色
     * @param normalCricleColor
     */
    public void setNormalCricleColor(@ColorInt int normalCricleColor)
    {
        this.normalCricleColor = normalCricleColor;
    }

    /**
     * 设置选中宫格颜色
     * @param selectCricleColor
     */
    public void setSelectCricleColor(@ColorInt int selectCricleColor)
    {
        this.selectCricleColor = selectCricleColor;
    }

    /**
     * 设置普通宫格内点的颜色
     * @param normalCriclePointColor
     */
    public void setNormalCriclePointColor(@ColorInt int normalCriclePointColor)
    {
        this.normalCriclePointColor = normalCriclePointColor;
    }

    /**
     * 设置选中宫格内点的颜色
     * @param selectCriclePointColor
     */
    public void setSelectCriclePointColor(@ColorInt int selectCriclePointColor)
    {
        this.selectCriclePointColor = selectCriclePointColor;
    }

    /**
     * 设置线条颜色
     * @param lineColor
     */
    public void setLineColor(@ColorInt int lineColor)
    {
        this.lineColor = lineColor;
    }

    /**
     * 设置线条顶端颜色
     * @param topLineColor
     */
    public void setTopLineColor(@ColorInt int topLineColor)
    {
        this.topLineColor = topLineColor;
    }
    
    /**
     * 设置宫格环宽度
     * @param circleStrokeWidth
     */
    public void setCircleStrokeWidth(int circleStrokeWidth)
    {
        this.circleStrokeWidth = circleStrokeWidth;
    }

    /**
     * 设置线条宽度
     * @param lineStrokeWidth
     */
    public void setLineStrokeWidth(int lineStrokeWidth)
    {
        this.lineStrokeWidth = lineStrokeWidth;
    }
    
    /**
     * 设置线条是否在最后一个宫格内显示
     * @param showInLastCircle
     */
    public void setShowInLastCircle(boolean showInLastCircle)
    {
        this.showInLastCircle = showInLastCircle;
    }
    
    /**
     * 设置最小选中数量
     * @param minSeleted
     */
    public void setMinSeleted(int minSeleted)
    {
        this.minSeleted = minSeleted;
    }

    public void setOnSelectListener(OnSelectListener onSelectListener)
    {
        this.onSelectListener = onSelectListener;
    }

    /**
     * 选中完成监听事件
     */
    public static interface OnSelectListener 
    {
        void onSelected(List<Cell> list);
    }
}

style.xml

<declare-styleable name="LockView">
        <!-- 九宫格数量 -->
        <attr name="pointCount" format="integer"/>
        <!--是否在最后一个宫格显示连线-->
        <attr name="showInLastCircle" format="boolean"/>
        <!--线的宽度-->
        <attr name="lineStrokeWidth" format="integer"/>
        <!--圆形环的的宽度-->
        <attr name="circleStrokeWidth" format="integer"/>
        <!--连线顶端颜色-->
        <attr name="topLineColor" format="color"/>
        <!--连线的颜色-->
        <attr name="lineColor" format="color"/>
        <!--选中的宫格内点的颜色-->
        <attr name="selectCriclePointColor" format="color"/>
        <!--普通宫格内点的颜色-->
        <attr name="normalCriclePointColor" format="color"/>
        <!--普通宫格环的颜色-->
        <attr name="normalCricleColor" format="color"/>
        <!--选中宫格环的颜色-->
        <attr name="selectCricleColor" format="color"/>
    </declare-styleable>

上图:

普通的效果

输入图片说明

修改的自己颜色

输入图片说明

十六宫格

输入图片说明

自定义颜色

输入图片说明