在 Android 中動態添加 View 並實現隨機位置且不重疊,是實現標籤雲、氣泡佈局、遊戲元素隨機生成等場景的常見需求。
RelativeLayout 或 FrameLayout,支援子 View 絕對定位LayoutParams 設定 leftMargin/topMargin 或 x/y 實現絕對定位List 記錄已佔用區域,新增 View 前檢測是否與現有矩形相交public class NonOverlapLayout extends RelativeLayout {
private final List occupiedRects = new ArrayList<>();
private final Random random = new Random();
private final int maxAttempts = 50;
public NonOverlapLayout(Context context) { super(context); }
public NonOverlapLayout(Context context, AttributeSet attrs) { super(context, attrs); }
public void addRandomView(View child, int width, int height) {
Rect childRect = findNonOverlappingPosition(width, height);
if (childRect == null) return; // 無法找到位置
LayoutParams params = new LayoutParams(width, height);
params.leftMargin = childRect.left;
params.topMargin = childRect.top;
addView(child, params);
occupiedRects.add(childRect);
}
private Rect findNonOverlappingPosition(int w, int h) {
int containerW = getWidth();
int containerH = getHeight();
if (containerW <= 0 || containerH <= 0) {
// 佈局未渲染完成,延遲執行
post(() -> addRandomView(getChildAt(getChildCount()-1), w, h));
return null;
}
for (int i = 0; i < maxAttempts; i++) {
int left = random.nextInt(containerW - w);
int top = random.nextInt(containerH - h);
Rect candidate = new Rect(left, top, left + w, top + h);
boolean overlap = false;
for (Rect existing : occupiedRects) {
if (Rect.intersects(candidate, existing)) {
overlap = true;
break;
}
}
if (!overlap) return candidate;
}
return null; // 重試失敗
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
// 容器大小改變時可選擇重新佈局
}
}
NonOverlapLayout container = findViewById(R.id.container);
for (int i = 0; i < 20; i++) {
TextView tv = new TextView(this);
tv.setText("Item " + i);
tv.setBackgroundColor(Color.HSVToColor(new float[]{random.nextInt(360), 0.5f, 0.9f}));
tv.setPadding(16, 8, 16, 8);
// 測量 View 寬高
tv.measure(0, 0);
int w = tv.getMeasuredWidth();
int h = tv.getMeasuredHeight();
container.addRandomView(tv, w, h);
}
<com.example.NonOverlapLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#f0f0f0" />
將容器劃分為網格,每個格子標記佔用狀態,隨機選擇連續空閒格子,大幅減少碰撞檢測次數。
// 添加時帶入場動畫
child.setAlpha(0f);
child.setScaleX(0.5f);
child.setScaleY(0.5f);
child.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(300).start();
總結:動態添加 View 核心在於「容器選擇 + 絕對定位 + 碰撞檢測」。對於簡單場景自寫邏輯即可;複雜場景建議使用成熟的 FlexboxLayout 或 RecyclerView 方案,更穩健高效。