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); }public boolean addViewToCell(View child, int cellX, int cellY, int spanX, int spanY) {if (!isRegionVacant(cellX, cellY, spanX, spanY)) {return false; }markCells(cellX, cellY, spanX, spanY, true);LayoutParams lp = new LayoutParams(cellX, cellY, spanX, spanY);!!! addView() 内部会调用 requestLayout() + invalidate(),强制容器更新 UI!!!这里的view ,以下的这些都可以添加进去!!!--->(基础控件 ✅ TextView、Button、ImageView 等可直接添加并显示。自定义 View ✅ 继承自 View 或现有控件,需正确实现 onDraw() 和 onMeasure()。ViewGroup 容器 ✅ 如 LinearLayout、FrameLayout,可作为子容器嵌套。)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); }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容器)
public class ShortcutsAndWidgetsContainer extends ViewGroup {private int mCellWidth, mCellHeight; public ShortcutsAndWidgetsContainer(Context context) {super(context);}public void setCellDimensions(int cellWidth, int cellHeight) {mCellWidth = cellWidth;mCellHeight = cellHeight;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {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) {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; }
}
3. 使用示例
CellLayout cellLayout = new CellLayout(context);
setContentView(cellLayout);
ImageView icon = new ImageView(context);
icon.setImageResource(R.drawable.ic_launcher);
cellLayout.addViewToCell(icon, 1, 2, 1, 1);
View widget = new View(context);
widget.setBackgroundColor(Color.BLUE);
cellLayout.addViewToCell(widget, 0, 0, 2, 2);
关键交互流程图
总结
组件 | 职责 |
---|
CellLayout | 管理网格参数、占用状态、处理高层的交互逻辑(如拖拽) |
ShortcutsAndWidgetsContainer | 实际承载子View,负责具体的测量和布局(像素级计算) |
mOccupied | 二维数组快速记录单元格占用状态,避免遍历所有子View |