手把手帶你玩轉 DialogFragment

前言

本文已經收錄到我的 Github 個人部落格,歡迎大佬們光臨寒舍:

我的 GIthub 部落格

思維導圖

一、為什麼要學習 DialogFragment

你還在用 Dialog 嗎?

你還在經常煩惱於螢幕翻轉的時候,Dialog 的各種奇葩情況嗎?

你想降低耦合嗎?

如果你有其中的一個煩惱,那麼恭喜你,遇見了 DialogFragment ,他恰巧就解決了上面所說的問題,如果感興趣的話,隨筆者來看下吧!

二、背景

Android 官方推薦使用 DialogFragment 來代替 Dialog ,可以讓它具有更高的可復用性(降低耦合)和更好的便利性(很好的處理螢幕翻轉的情況)。

而創建 DialogFragment 有兩種方式:

  • 法一:覆寫其 onCreateDialog 方法

一般用於創建替代傳統Dialog 對話框的場景,UI 簡單功能單一,不適用於使用了多執行緒(例如網路請求)的情況下(因為不能正確的獲取當前 Fragment 的狀態,會產生空指針異常)

  • 法二:覆寫其 onCreateView 方法

一般用於創建複雜內容彈窗全螢幕展示效果的場景,UI 複雜功能複雜一般有網路請求等非同步操作

三、應用

3.1 基本用法是什麼

法一:

a.創建一個簡單的 Dialog 並返回它即可

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // 設置主題的構造方法
    // AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
    builder.setTitle("注意:")
           .setMessage("是否退出應用?")
           .setPositiveButton("確定", null)
           .setNegativeButton("取消", null)
           .setCancelable(false);
           //builder.show(); // 不能在這裡使用 show() 方法
    return builder.create();
}
運行截圖
運行截圖

b.你也可以使用自定義 View 來創建:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // 設置主題的構造方法
    // AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
    LayoutInflater inflater = getActivity().getLayoutInflater();  
    View view = inflater.inflate(R.layout.fragment_dialog, null);  
    builder.setView(view) 
    // Do Someting,eg: TextView tv = view.findViewById(R.id.tv);
    return builder.create();
}
運行截圖
運行截圖

PS:創建 Dialog 的方式有多種,比如下面這種,使用時略有差異,需要自己注意:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();
    View view = inflater.inflate(R.layout.fragment_dialog, null);
    Dialog dialog = new Dialog(getActivity());
    // 設置主題的構造方法
    // Dialog dialog = new Dialog(getActivity(), R.style.CustomDialog);
    dialog.setContentView(view);
    // Do Someting
    return dialog;
}
運行截圖
運行截圖

這種情況,標題內容上面的白色部分,其實是默認的標題欄,如果需要的話,可以設置隱藏標題欄(將在下文說到)

3.2 如何處理螢幕翻轉

如果使用傳統的 Dialog ,需要我們手動處理螢幕翻轉的情況,但使用 DialogFragment 的話,則不需要我們進行任何處理,FragmentManager 會自動管理 DialogFragment 的生命周期。

3.3 如何隱藏標題欄

在基本用法里程式碼注釋有設置主題的地方,下面詳細說下兩種方法下設置無標題欄的方式:

法一:

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = Objects.requireNonNull(getActivity()).getLayoutInflater();
    @SuppressLint("InflateParams") View view = inflater.inflate(R.layout.fragment_i_o_s_dialog, null);
    Dialog dialog = new Dialog(getActivity());
    // 關閉標題欄,setContentView() 之前調用
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.setContentView(view);
    dialog.setCanceledOnTouchOutside(true);
    return dialog;
}

法二:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setStyle(DialogFragment.STYLE_NO_TITLE, 0);
}

3.4 如何實現全螢幕

常用的形式大多是寬度上和螢幕一樣寬,高度自適應,下面直接看程式碼:

法一:

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();
    View view = inflater.inflate(R.layout.fragment_dialog, null);
    Dialog dialog = new Dialog(getActivity(), 0);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    dialog.setContentView(view);
    dialog.setCanceledOnTouchOutside(true);
    //Do something
    // 設置寬度為屏寬、位置靠近螢幕底部
    Window window = dialog.getWindow();
    //設置了窗口的背景色為透明,這一步是必須的
    // <color name="transparent">#50000000</color>
    window.setBackgroundDrawableResource(R.color.transparent);
    WindowManager.LayoutParams wlp = window.getAttributes();
    wlp.gravity = Gravity.BOTTOM;
    //設置窗口的寬度為 MATCH_PARENT,效果是和螢幕寬度一樣大
    wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
    wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
    window.setAttributes(wlp);
    return dialog;
}

法二:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setStyle(DialogFragment.STYLE_NO_TITLE, 0);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        getDialog().setCanceledOnTouchOutside(true);
        View rootView = inflater.inflate(R.layout.fragment_dialog, container, false);
        //Do something
        // 設置寬度為屏寬、靠近螢幕底部。
        final Window window = getDialog().getWindow();
     //這步是必須的
        window.setBackgroundDrawableResource(R.color.transparent);
     //必要,設置 padding,這一步也是必須的,內容不能填充全部寬度和高度
        window.getDecorView().setPadding(0, 0, 0, 0);
        WindowManager.LayoutParams wlp = window.getAttributes();
        wlp.gravity = Gravity.BOTTOM;
        wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
        wlp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        window.setAttributes(wlp);
        return rootView;
}

3.5 應用場景的區別是什麼

文章一開始簡單總結了法一 和法二的應用場景,這裡說明下:

  • 法一:為簡單的替代 Dialog 提供了非常方便的創建方式,但是在使用了多執行緒(例如網路請求)的情況下,不能正確的獲取當前 Fragment 的狀態,會產生空指針異常
  • 法二:則沒有如上空指針的問題,而且,其創建方式默認使用了自定義 View,更便於應對複雜 UI 的場景

3.6 如何與 Activity 進行交互?

使用回調的方式

a.在 DialogFragment 中:

public interface OnDialogListener {
    void onDialogClick(String person);
}

private OnDialogListener mlistener;

public void setOnDialogListener(OnDialogListener dialogListener){
    this.mlistener = dialogListener;
}

DialogFragment 的點擊事件中:

public OnDialogListener mlistener;
@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.tv1:
            mlistener.onDialogClick("1");
            dismiss();
            break; 
       case R.id.tv2:
            mlistener.onDialogClick("2");
            dismiss();
            break;
        case R.id.tv3:
            mlistener.onDialogClick("3");
            dismiss();
            break;
        case R.id.tv4:
            mlistener.onDialogClick("4");
            dismiss();
            break;
    }
}

b.在 Activity

dialogFragment.setOnDialogListener(new PersonDialogFragment.OnDialogListener() {
    @Override
    public void onDialogClick(String person) {
        ToastUtil.showToast(person);
    }
});

3.7 如何結合動畫使用

a.設置從下到上彈出的動畫

private void slideToUp(View view) {
    Animation slide = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0);
    slide.setDuration(400);
    slide.setFillEnabled(true);
    slide.setFillAfter(true);
    view.startAnimation(slide);
}

b.設置從上到下彈出的動畫

private boolean isAnimation = false;//用來判斷是否多次點擊。防止多次執行

public void slideToDown(View view) {
    Animation slide = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, 0.0f, 
            Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, 1.0f);
    slide.setDuration(400);
    slide.setFillEnabled(true);
    slide.setFillAfter(true);
    view.startAnimation(slide);
    slide.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            //用來判斷是否多次點擊。防止多次執行
            isAnimation = false;
            //彈框消失
            IOSDialogFragment.this.dismiss();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });
}

c.封裝從上到下彈出的動畫

加上判斷是否多次點擊。防止多次執行

private void dialogFinish() {
    if (isAnimation) {
        return;
    }
    isAnimation = true;
    slideToDown(rootView);
}

3.8 如何在 Activity 彈出 DialogFragment ?

mBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        IOSDialogFragment fragment = new IOSDialogFragment();
        //第二個參數是 tag
        fragment.show(getSupportFragmentManager(), "android");
    }
});

3.9 如何點擊空白處時關閉的時候,還能使用動畫?

直接對 DecorView 設置 onTouchListener

window.getDecorView().setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            //彈框消失的動畫執行相關程式碼
            ....
            ....
        
        }
        return true;
    }
});

四、結語

終於看完了鴨!累死鴨了!如果還有什麼不是很清楚的話,可以看下筆者寫的示例 Demo


如果文章對您有一點幫助的話,希望您能點一下贊,您的點贊,是我前進的動力

本文參考鏈接:

本文使用 mdnice 排版