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 ) ;
关键交互流程图
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