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

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. 生成布局参数并添加ViewLayoutParams 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;}}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 计算总宽高(单元格尺寸 × 数量)int width = mCellWidth * mCountX;int height = mCellHeight * mCountY;setMeasuredDimension(width, height);mContainer.measure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected 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;}@Overrideprotected 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);}@Overrideprotected 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());}}@Overrideprotected 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