Skip to content

AdvancedExtendedUsage_tc

Kongzue edited this page Feb 8, 2025 · 5 revisions

🌐 View English Document | 繁體中文文檔

🆙 高階擴展用法

DialogX 結構圖

DialogX 結構圖

單獨針對組件生效的屬性

儘管已經有全局的配置,但開發者依然可以單獨針對某一個組件進行一些設置。

例如如果全局配置為禁止使用點擊外部區域關閉對話框,而要 BottomDialog 例外,那麼可以使用如下配置:

BottomDialog.overrideCancelable = BaseDialog.BOOLEAN.TRUE;

還可以針對組件的動畫進行單獨設置,例如:

BottomDialog.overrideEnterDuration = 100;    //入場動畫時長為100毫秒
BottomDialog.overrideExitDuration = 100;     //出場動畫時長為100毫秒
BottomDialog.overrideEnterAnimRes = R.anim.anim_dialogx_top_enter;  //入場動畫資源
BottomDialog.overrideExitAnimRes = R.anim.anim_dialogx_top_exit;    //出場動畫資源

這樣的方法單獨對某一個組件進行進出場動畫的重寫設置。注意這些方法不會影響全局其他組件,只是單獨設置單一組件的動畫效果。

優先級為:實例使用方法設置 > 組件override設置 > 全局設置。

另外 FullScreenDialog 只提供動畫時長設置,不提供動畫資源設置(此組件只允許上下進出場動畫);

CustomDialog 額外擁有了 overrideMaskEnterAnimResoverrideMaskExitAnimRes,可以覆蓋背景遮罩的動畫效果,設置為 0 時取消動畫。

此時,在當前項目中其他的對話框組件將依然遵循全局配置,而單獨針對組件生效的屬性的對話框則會以單獨屬性設置為主。

注意此配置要區別於針對單一對話框實例的屬性設置,例如:

BottomDialog.build()
        ...
        .setCancelable(true)   
        .show();

重寫部分主題

例如想要將 PopTip 多士提示不按照主題的設定(例如螢幕底部)顯示,而是以自己的要求顯示(例如螢幕中央),但對於 PopTip 的 align 屬性屬於主題控制的,此時可以通過重寫主題來調整對話框的部分行為,例如:

DialogX.globalStyle = new MaterialStyle(){
    @Override
    public PopTipSettings popTipSettings() {
        return new PopTipSettings() {
            @Override
            public ALIGN align() {
                return ALIGN.CENTER;
            }
        };
    }
};

完全自定義開啟、關閉動畫

請注意此方法針對的是需要對對話框內容組件和細節完全自行控制的情況,如果你只是想改變整體動畫而不需要控制細節那麼請參閱某種對話框的自定義進入和關閉動畫章節,例如 《基礎對話框 MessageDialog 和 輸入對話框 InputDialog - 自定義進入和關閉動畫》

使用 DialogXAnimInterface 接口可以完全自定義開啟、關閉動畫。

由於 DialogX 對話框組件的內部元素都是暴露的,你可以輕鬆獲取並訪問內部實例,利用這一點,再加上 DialogXAnimInterface 會負責對話框啟動和關閉的動畫行為,你可以充分利用它實現你想要的效果。

例如對於一個 CustomDialog,你可以這樣控制其啟動和關閉動畫:

CustomDialog.show(new OnBindView<CustomDialog>(R.layout.layout_custom_dialog) {
            @Override
            public void onBind(final CustomDialog dialog, View v) {
                //...
            }
        })
        //實現完全自定義動畫效果
        .setDialogXAnimImpl(new DialogXAnimInterface<CustomDialog>() {
            @Override
            public void doShowAnim(CustomDialog customDialog, ViewGroup dialogBodyView) {
                Animation enterAnim;
                int enterAnimResId = com.kongzue.dialogx.R.anim.anim_dialogx_top_enter;
                enterAnim = AnimationUtils.loadAnimation(me, enterAnimResId);
                enterAnim.setInterpolator(new DecelerateInterpolator(2f));
                long enterAnimDurationTemp = enterAnim.getDuration();
                enterAnim.setDuration(enterAnimDurationTemp);
                customDialog.getDialogImpl().boxCustom.startAnimation(enterAnim);
                ValueAnimator bkgAlpha = ValueAnimator.ofFloat(0f, 1f);
                bkgAlpha.setDuration(enterAnimDurationTemp);
                bkgAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        if (customDialog.getDialogImpl() == null || customDialog.getDialogImpl().boxRoot == null) {
                            return;
                        }
                        customDialog.getDialogImpl().boxRoot.setBkgAlpha((Float) animation.getAnimatedValue());
                    }
                });
                bkgAlpha.start();
            }
            @Override
            public void doExitAnim(CustomDialog customDialog, ViewGroup dialogBodyView) {
                int exitAnimResIdTemp = com.kongzue.dialogx.R.anim.anim_dialogx_default_exit;
                Animation exitAnim = AnimationUtils.loadAnimation(me, exitAnimResIdTemp);
                customDialog.getDialogImpl().boxCustom.startAnimation(exitAnim);
                ValueAnimator bkgAlpha = ValueAnimator.ofFloat(1f, 0f);
                bkgAlpha.setDuration(exitAnim.getDuration());
                bkgAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        if (customDialog.getDialogImpl() == null || customDialog.getDialogImpl().boxRoot == null) {
                            return;
                        }
                        customDialog.getDialogImpl().boxRoot.setBkgAlpha((Float) animation.getAnimatedValue());
                    }
                });
                bkgAlpha.start();
            }
        });

對於 animProgress 它本質上是個反向回調執行器,因為動畫時長不定,你需要通知 DialogX 當前你的動畫到達哪個階段了,對話框需要根據這個階段進行操作處理,例如關閉動畫執行過程應當是 1f 至 0f 的過程,完畢後應當銷毀對話框,那麼當 animProgress.run(0f) 時就會執行銷毀流程,而啟動動畫應當是 0f 至 1f 的過程,當 animProgress.run(1f) 時啟動對話框的動畫完全執行完畢。

隊列對話框

某些場景下需要有“模態”對話框的需要,即,一次性創建多個對話框,組成隊列,逐一顯示,當上一個對話框關閉時自動啟動下一個對話框,此時可以使用隊列對話框來完成。

示例代碼如下,在 DialogX.showDialogList(...) 中構建多個對話框,請注意這些對話框必須是沒有啟動的狀態,使用 .build() 方法完成構建,以 “,” 分隔組成隊列,即可自動啟動。

DialogX.showDialogList(
        MessageDialog.build().setTitle("提示").setMessage("這是一組消息對話框隊列").setOkButton("開始").setCancelButton("取消")
                .setCancelButton(new OnDialogButtonClickListener<MessageDialog>() {
                    @Override
                    public boolean onClick(MessageDialog dialog, View v) {
                        dialog.cleanDialogList();
                        return false;
                    }
                }),
        PopTip.build().setMessage("每個對話框會依次顯示"),
        PopNotification.build().setTitle("通知提示").setMessage("直到上一個對話框消失"),
        InputDialog.build().setTitle("請注意").setMessage("你必須使用 .build() 方法構建,並保證不要自己執行 .show() 方法").setInputText("輸入文字").setOkButton("知道了"),
        TipDialog.build().setMessageContent("準備結束...").setTipType(WaitDialog.TYPE.SUCCESS),
        BottomDialog.build().setTitle("結束").setMessage("下滑以結束旅程,祝你編碼愉快!").setCustomView(new OnBindView<BottomDialog>(R.layout.layout_custom_dialog) {
            @Override
            public void onBind(BottomDialog dialog, View v) {
                ImageView btnOk;
                btnOk = v.findViewById(R.id.btn_ok);
                btnOk.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                                        dialog.dismiss();
                                    }
                });
            }
        })
);

使用過程中,隨時可以使用 .cleanDialogList() 來停止接下來的隊列對話框的顯示。

實現模式(實驗性)

調整 DialogX 的實現模式有兩種方法,其一是全局調整,即直接修改 DialogX 的全局配置,例如:

DialogX.implIMPLMode= DialogX.IMPL_MODE.WINDOW;

另一個方式為單次修改,僅在一個彈窗中生效,例如:

MessageDialog.build()    //必須使用 build() 方法構建時指定 DialogImplMode 才會生效。
        .setDialogImplMode(DialogX.IMPL_MODE.WINDOW)
        .setTitle("Title text")
        .setMessage("Message content")
        .setOkButton("OK")
        .show();

Window 實現模式

DialogX 默認使用 View 實現模式,自適應生命週期,你也可以選擇 Window 的實現方案(與 AlertDialog 類似),請注意,使用 Window 實現模式是實驗性質的,且存在一定的風險和已知 bug,你可以使用以下方法啟用 Window 實現模式:

DialogX.implIMPLMode= DialogX.IMPL_MODE.WINDOW;

WindowUtil 提供了額外自定義WindowManager.LayoutParams的接口:

WindowUtil.windowSettings = new WindowUtil.WindowSettings() {
    @Override
    public WindowManager.LayoutParams overrideWindowLayoutParamsInterface(Context context, View dialogView, WindowManager.LayoutParams originWindowLayoutParams) {
        //處理 originWindowLayoutParams...
        return originWindowLayoutParams;
    }

    @Override
    public ViewGroup overrideRootView(Context context) {
        //自定義根佈局,對話框佈局會被添加到根佈局中,返回 null 代表使用默認的 FrameLayout
        return null;
    }
};

此外,如果 app 具有懸浮窗權限,在 Window 模式下還可以實現全局彈窗,請在初始化時使用以下代碼開啟:

DialogX.globalHoverWindow = true;

DialogFragment 實現方式

在最新的版本 0.0.41.beta4 中,提供了 DialogFragment 實現方式,使用以下方法啟用 DialogFragment 實現:

DialogX.implIMPLMode= DialogX.IMPL_MODE.DIALOG_FRAGMENT;

此模式下目前尚未發現待處理的問題,但依然不建議在生產環境使用。

FloatingActivity 實現方式

類似於 Window,使用一個透明底的 Activity 作為對話框的底層承擔顯示,這樣的做法可以再一定程度上從後台啟動(PendingIntent)。

DialogX.implIMPLMode= DialogX.IMPL_MODE.FLOATING_ACTIVITY;

背景模糊(Android 12+)

在 Android 12 以上的設備可以借助 RenderEffect 實現背景模糊效果。

背景模糊(Android 12+)

要在單一的對話框中實現此效果,可以利用生命週期事件對 activity 的元素添加模糊,從而達到背景模糊的效果,範例代碼如下:

.setDialogLifecycleCallback(new DialogLifecycleCallback<CustomDialog>() {
    @Override
    public void onShow(CustomDialog dialog) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            RenderEffect blurEffect = RenderEffect.createBlurEffect(20f, 20f, Shader.TileMode.CLAMP);
            ((ViewGroup) getWindow().getDecorView()).getChildAt(0).setRenderEffect(blurEffect);
        }
    }
    @Override
    public void onDismiss(CustomDialog dialog) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            ((ViewGroup) getWindow().getDecorView()).getChildAt(0).setRenderEffect(null);
        }
    }
})

如果要實現全局的對話框背景模糊效果,可以利用全局生命週期實現,注意以下代碼會過濾掉 PopTip 和 PopNotification,需要過濾其他的對話框請自行判斷處理:

DialogX.dialogLifeCycleListener = new DialogLifecycleCallback<BaseDialog>() {
    @Override
    public void onShow(BaseDialog dialog) {
        super.onShow(dialog);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S && !(dialog instanceof NoTouchInterface) ) {
            RenderEffect blurEffect = RenderEffect.createBlurEffect(20f, 20f, Shader.TileMode.CLAMP);
            ((ViewGroup) dialog.getOwnActivity().getWindow().getDecorView()).getChildAt(0).setRenderEffect(blurEffect);
        }
    }
    @Override
    public void onDismiss(BaseDialog dialog) {
        super.onDismiss(dialog);
        List<BaseDialog> sameActivityRunningDialog = BaseDialog.getRunningDialogList(dialog.getOwnActivity());
        Iterator<BaseDialog> iterator = sameActivityRunningDialog.iterator();
        while (iterator.hasNext()) {
            if (iterator.next() instanceof NoTouchInterface) {
                iterator.remove();
            }
        }
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S && !(dialog instanceof PopTip) && !(dialog instanceof PopNotification) && (sameActivityRunningDialog.isEmpty() || sameActivityRunningDialog.get(0) == dialog)) {
            ((ViewGroup) dialog.getOwnActivity().getWindow().getDecorView()).getChildAt(0).setRenderEffect(null);
        }
    }
};

請注意上述方法僅針對 Android 12+ 有效,如果無法調用 RenderEffect 請調整 app 的 targetSdkVersion 至 31,RenderEffect 是否可用取決於系統支持,在符合版本的系統下也可能存在不支持導致無效的情況。

事務穿透

數據穿透(對話框的臨時儲物櫃)

當需要在對話框生命週期內臨時存儲數據(如網絡請求結果),或在對話框構建時預置初始化數據時,可以使用數據穿透功能。

使用場景

  • 對話框不同組件間共享數據
  • 預存初始化參數供對話框使用
  • 臨時緩存用戶操作記錄
// 存儲數據(支持任意對象類型)
dialog.setData("userInfo", userObject)

// 獲取數據(自動類型適配)
User user = dialog.getData("userInfo"); 

動作穿透(對話框的快捷功能鍵)

當需要在對話框不同交互節點(顯示時/按鈕點擊時)復用相同業務邏輯,同時避免暴露對話框內部UI組件時,可使用預置動作功能。

使用場景

  • 多位置觸發相同業務邏輯(如數據刷新)
  • 封裝敏感操作防止外部調用
  • 解耦複雜業務邏輯

核心方法

// 預定義動作(示例為1號動作)
dialog.setActionRunnable(1, () -> {
    // 這裏編寫通用業務邏輯
    refreshData(); 
    updateUI();
});

// 執行預置動作(可在任意位置調用)
dialog.runAction(1);

舉例,我們有一個在對話框啟動後需要調用網絡請求加載數據,將結果刷新顯示對話框內的內容 UI 的業務,同時我們點擊對話框的按鈕執行提交後數據可能有所變化需要重新刷新對話框內容,此時因為按鈕回調接口和對話框啟動後顯示內容的邏輯不在同一層級(分屬於不同的內部類)無法互相調用,要解決此問題就可以使用動作穿透來實現,例如:

MessageDialog.show("標題", "這裏是正文內容。", "確定")
  	//在對話框顯示後執行...
        .onShow(new DialogXRunnable<MessageDialog>() {
            @Override
            public void run(MessageDialog dialog) {
                //預置操作:動作1
                dialog.setActionRunnable(1, new DialogXRunnable<MessageDialog>() {
                    @Override
                    public void run(MessageDialog dialog) {
                        tip("action 1 run!");
                        //舉例:需要通過網絡加載數據到 dialog 中顯示
                    }
                });
                //執行動作1
                dialog.runAction(1);
            }
        })
  	//點擊按鈕後執行
        .setOkButton(new OnDialogButtonClickListener<MessageDialog>() {
            @Override
            public boolean onClick(MessageDialog dialog, View v) {
                PopTip.show("點擊確定按鈕");
                //舉例:點擊ok按鈕後執行了一些操作但需要在不關掉對話框情況下重新加載執行動作1
                //穿透執行動作1
                dialog.runAction(1);
                return true;
            }
        });

設計優勢

  1. 安全隔離:業務邏輯封裝在對話框內部,不暴露UI組件
  2. 邏輯復用:同一操作可在不同場景觸發(顯示時/按鈕點擊後)
  3. 靈活控制:通過動作編號管理多個獨立業務流程
  4. 生命週期安全:自動跟隨對話框銷毀釋放資源
Clone this wiki locally