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;
}
}
}
@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);
}
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;
}
@Override
protected 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);
}
@Override
protected 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()
);
}
}
@Override
protected 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 |