Launcher3中的CellLayout 和ShortcutAndWidgetContainer 的联系和各自职责

发布于:2025-06-29 ⋅ 阅读:(18) ⋅ 点赞:(0)

1. CellLayout(网格布局容器)

/**
 * 网格布局的核心容器,负责划分单元格和管理占用状态
 */
public class CellLayout extends ViewGroup {
    private int mCellWidth = 100;  // 单元格宽度(像素)
    private int mCellHeight = 100; // 单元格高度
    private int mCountX = 4;      // 列数
    private int mCountY = 4;      // 行数
    
    !!!一个二维的boolean类型数据的数组,里面的每个数据都是boolean类型!!!
    private boolean[][] mOccupied = new boolean[mCountX][mCountY]; // 占用状态表
    
    private ShortcutsAndWidgetsContainer mContainer; // 子容器

    public CellLayout(Context context) {
        super(context);
        mContainer = new ShortcutsAndWidgetsContainer(context);
        addView(mContainer); // 添加唯一子容器
    }

    /**
     * 添加子View到指定单元格
     * @return 是否添加成功
     */
    public boolean addViewToCell(View child, int cellX, int cellY, int spanX, int spanY) {
        // 1. 检查边界和占用状态
        if (!isRegionVacant(cellX, cellY, spanX, spanY)) {
            return false; // 区域被占用或越界
        }

        // 2. 标记单元格为已占用
        markCells(cellX, cellY, spanX, spanY, true);

        // 3. 生成布局参数并添加View
        LayoutParams lp = new LayoutParams(cellX, cellY, spanX, spanY);


!!!	addView() 内部会调用 requestLayout() + invalidate(),强制容器更新 UI!!!
		这里的view ,以下的这些都可以添加进去!!!--->
		(基础控件	✅	TextViewButtonImageView 等可直接添加并显示。
		自定义 View	✅	继承自 View 或现有控件,需正确实现 onDraw()onMeasure()ViewGroup 容器	✅	如 LinearLayoutFrameLayout,可作为子容器嵌套。)
	
        mContainer.addView(child, lp);

        return true;
    }

    /** 检查目标区域是否全部空闲 */
    private boolean isRegionVacant(int cellX, int cellY, int spanX, int spanY) {
        for (int x = cellX; x < cellX + spanX; x++) {
            for (int y = cellY; y < cellY + spanY; y++) {
                if (x >= mCountX || y >= mCountY || mOccupied[x][y]) {
                    return false; // 越界或已被占用
                }
            }
        }
        return true;
    }

    /** 标记/清除单元格占用状态 */
    private void markCells(int cellX, int cellY, int spanX, int spanY, boolean occupied) {
        for (int x = cellX; x < cellX + spanX; x++) {
            for (int y = cellY; y < cellY + spanY; y++) {
                mOccupied[x][y] = occupied;
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 计算总宽高(单元格尺寸 × 数量)
        int width = mCellWidth * mCountX;
        int height = mCellHeight * mCountY;
        setMeasuredDimension(width, height);
        mContainer.measure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mContainer.layout(0, 0, r - l, b - t); // 子容器填满父布局
    }

    /** 自定义LayoutParams,携带单元格信息 */
    public static class LayoutParams extends ViewGroup.LayoutParams {
        public int cellX, cellY; // 起始单元格坐标
        public int spanX, spanY; // 跨单元格数

        public LayoutParams(int cellX, int cellY, int spanX, int spanY) {
            super(0, 0);
            this.cellX = cellX;
            this.cellY = cellY;
            this.spanX = spanX;
            this.spanY = spanY;
        }
    }
}

2. ShortcutsAndWidgetsContainer(子View容器)

/**
 * 实际承载所有子View的容器,负责具体布局
 */
public class ShortcutsAndWidgetsContainer extends ViewGroup {
    private int mCellWidth, mCellHeight; // 从CellLayout接收的单元格尺寸

    public ShortcutsAndWidgetsContainer(Context context) {
        super(context);
    }

    /** 接收CellLayout传递的网格参数 */
    public void setCellDimensions(int cellWidth, int cellHeight) {
        mCellWidth = cellWidth;
        mCellHeight = cellHeight;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 测量所有子View(根据跨单元格数计算实际尺寸)
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
            int childWidth = lp.spanX * mCellWidth;
            int childHeight = lp.spanY * mCellHeight;
            child.measure(
                MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
            );
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 遍历所有子View,根据单元格坐标计算实际位置
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
            int left = lp.cellX * mCellWidth;
            int top = lp.cellY * mCellHeight;
            child.layout(
                left,
                top,
                left + child.getMeasuredWidth(),
                top + child.getMeasuredHeight()
            );
        }
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof CellLayout.LayoutParams; // 只接受自定义LayoutParams
    }
}

3. 使用示例

// 初始化CellLayout(4x4网格)
CellLayout cellLayout = new CellLayout(context);
setContentView(cellLayout);

// 添加一个1x1的图标到(1,2)位置
ImageView icon = new ImageView(context);
icon.setImageResource(R.drawable.ic_launcher);
cellLayout.addViewToCell(icon, 1, 2, 1, 1);

// 添加一个2x2的小部件到(0,0)位置
View widget = new View(context);
widget.setBackgroundColor(Color.BLUE);
cellLayout.addViewToCell(widget, 0, 0, 2, 2);

关键交互流程图

User CellLayout ShortcutsAndWidgetsContainer icon 调用addViewToCell(icon, 1, 2, 1, 1) 检查mOccupied[1][2]是否空闲 标记mOccupied[1][2]=true addView(icon, LayoutParams) 计算实际像素位置(x=100,y=200) layout(100,200,200,300) User CellLayout ShortcutsAndWidgetsContainer icon

总结

组件 职责
CellLayout 管理网格参数、占用状态、处理高层的交互逻辑(如拖拽)
ShortcutsAndWidgetsContainer 实际承载子View,负责具体的测量和布局(像素级计算)
mOccupied 二维数组快速记录单元格占用状态,避免遍历所有子View

网站公告

今日签到

点亮在社区的每一天
去签到