自定義 `Dialog`/`AlertDialog` 默認顯示在標題欄(ActionBar/StatusBar)下方,嘗試設置 `layoutParams.y = -50` 等負值向上偏移無效,`getWindow().setGravity(Gravity.TOP)` 也無法突破標題欄限制。
`Dialog` 依附於 `Window`,其 `WindowManager.LayoutParams` 的 `y` 坐標在 `PhoneWindow` 內部會被修正:最小值為標題欄高度。這是系統為了保證 Dialog 不遮擋狀態欄/導航欄的安全機制,不可繞過。
`PopupWindow` 是浮動容器,不受標題欄約束,可精確控制屏幕任意座標顯示。
public class TopAlignedPopup {
private PopupWindow popupWindow;
private View contentView;
public TopAlignedPopup(Context context) {
// 1. 加載佈局
contentView = LayoutInflater.from(context).inflate(R.layout.popup_top, null);
// 2. 初始化 PopupWindow (寬高自適應)
popupWindow = new PopupWindow(contentView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true); // focusable = true 允許外部點擊消失
// 3. 關鍵:設置背景才能響應外部點擊消失
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 4. 可選:動畫
popupWindow.setAnimationStyle(R.style.PopupAnimation_Top);
}
// 顯示在屏幕頂部 (0,0)
public void showAtTop(View anchor) {
// anchor 可為任意 View,僅用於獲取 WindowToken
popupWindow.showAtLocation(anchor, Gravity.TOP | Gravity.START, 0, 0);
}
// 顯示在指定座標
public void showAtLocation(View anchor, int x, int y) {
popupWindow.showAtLocation(anchor, Gravity.NO_GRAVITY, x, y);
}
public void dismiss() {
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.dismiss();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white"
android:elevation="8dp"> <!-- 陰影 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="頂部對齊彈窗"
android:padding="16dp"
android:gravity="center"
android:textSize="16sp" />
<Button
android:id="@+id/btn_close"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="關閉" />
</LinearLayout>
TopAlignedPopup popup = new TopAlignedPopup(this);
popup.showAtTop(findViewById(android.R.id.content)); // 以根佈局為錨點
contentView.findViewById(R.id.btn_close).setOnClickListener(v -> popup.dismiss());
| 特性 | Dialog / AlertDialog | PopupWindow |
|---|---|---|
| 位置控制 | 受標題欄/裝飾限制,難以頂部貼邊 | ✅ 任意座標、任意錨點 |
| 生命周期 | 綁定 Activity,自動管理 | 手動管理,需防洩漏 |
| 模態/阻斷 | ✅ 原生模態 (setCancelable) | 需手動設置 `setFocusable(true)` + 背景 |
| 輸入法處理 | 自動彈出/收起 | 需手動 `setSoftInputMode` |
| 適用場景 | 確認框、底部彈窗、表單 | 頂部通知、下拉菜單、Tooltip、自定義位置 |
Dialog 位置受限於系統窗口裝飾,**需頂部貼邊/任意座標時請直接用 PopupWindow**。封裝通用 `PopupHelper` 管理生命周期、動畫、輸入法、模態遮罩,即可獲得比 Dialog 更靈活、更強大的浮動交互能力。