Android 擼起袖子,自己封裝 DialogFragment

developerHaoz發表於2017-09-12

前言

具體的程式碼以及示例我都放上 Github 了,有需要的朋友可以去看一下 DialogFragmentDemos,歡迎 star 和 fork.

本文的主要內容

  • DialogFragment 是什麼
  • 建立通用的 CommonDialogFragment
  • 實現各種型別的 DialogFragment

在寫正文之前,先來一波效果展示吧

DialogFragmentDemos.gif
DialogFragmentDemos.gif

一、DialogFragment 是什麼

DialogFragment 在 Android 3.0 時被引入,是一種特殊的 Fragment,用於在 Activity 的內容之上顯示一個靜態的對話方塊。例如:警告框、輸入框、確認框等。

1、DialogFragment 的優點

其實在 Android 中顯示對話方塊有兩種型別可供使用,一種是 DialogFragment,而另一種則是 Dialog。在 DialogFragment 產生之前,我們建立對話方塊一般採用 Dialog,而且從程式碼的編寫角度來看,Dialog 使用起來其實更加簡單,但是 Google 卻是推薦儘量使用 DialogFragment,是不是感覺很奇怪,其實原因也很簡單, DialogFragment 有著 Dialog 所沒有的非常好的特性

  • DialogFragment 本身是 Fragment 的子類,有著和 Fragment 基本一樣的生命週期,使用 DialogFragment 來管理對話方塊,當旋轉螢幕和按下後退鍵的時候可以更好的管理其生命週期
  • 在手機配置變化導致 Activity 需要重新建立時,例如旋轉螢幕,基於 DialogFragment 的對話方塊將會由 FragmentManager 自動重建,然而基於 Dialog 實現的對話方塊卻沒有這樣的能力

2、DialogFragment 的使用

使用 DialogFragment 至少需要實現 onCreateView() 或者 onCreateDialog() 方法,onCreateView() 即使用自定義的 xml 佈局檔案來展示 Dialog,而 onCreateDialog() 即使用 AlertDialog 或者 Dialog 建立出 我們想要的 Dialog,因為這篇文章主要是講 DialogFragment 的封裝,至於 DialogFragment 具體的使用,可以參考下洋神的這篇文章 Android 官方推薦 : DialogFragment 建立對話方塊

二、建立通用的 CommonDialogFragment

這個類是 DialogFragment 的子類,對 DialogFragment 進行封裝,依賴外部傳入的 AlertDialog 來構建,同時也處理了 DialogFragment 中 AlertDialog 不能設定外部取消的問題

public class CommonDialogFragment extends DialogFragment {

    /**
     * 監聽彈出窗是否被取消
     */
    private OnDialogCancelListener mCancelListener;

    /**
     * 回撥獲得需要顯示的dialog
     */
    private OnCallDialog mOnCallDialog;

    public interface OnDialogCancelListener {
        void onCancel();
    }

    public interface OnCallDialog {
        Dialog getDialog(Context context);
    }

    public static CommonDialogFragment newInstance(OnCallDialog call, boolean cancelable) {
        return newInstance(call, cancelable, null);
    }

    public static CommonDialogFragment newInstance(OnCallDialog call, boolean cancelable, OnDialogCancelListener cancelListener) {
        CommonDialogFragment instance = new CommonDialogFragment();
        instance.setCancelable(cancelable);
        instance.mCancelListener = cancelListener;
        instance.mOnCallDialog = call;
        return instance;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        if (null == mOnCallDialog) {
            super.onCreateDialog(savedInstanceState);
        }
        return mOnCallDialog.getDialog(getActivity());
    }

    @Override
    public void onStart() {
        super.onStart();
        Dialog dialog = getDialog();
        if (dialog != null) {
            //在5.0以下的版本會出現白色背景邊框,若在5.0以上設定則會造成文字部分的背景也變成透明
            if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                //目前只有這兩個dialog會出現邊框
                if(dialog instanceof ProgressDialog || dialog instanceof DatePickerDialog) {
                    getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
                }
            }
            Window window = getDialog().getWindow();
            WindowManager.LayoutParams windowParams = window.getAttributes();
            windowParams.dimAmount = 0.0f;
            window.setAttributes(windowParams);
        }
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        super.onCancel(dialog);
        if (mCancelListener != null) {
            mCancelListener.onCancel();
        }
    }

}複製程式碼

可以看到這個類的程式碼量也是很少的,先定義了兩個介面 OnDialogCancelListener,OnCallDialog,前者用於監聽彈出窗是否被取消,後者則可以讓我們回撥獲得想要顯示的 Dialog,可以看到在 onCreateDialog() 中我們返回的 是 mOnCallDialog.getDialog(getActivity);,當我們在傳入 Dialog 的時候,便會回撥到此處,讓 onCreateDialog() 返回我們傳入的 Dialog,對介面回撥不是很清楚的朋友,可以看下這篇文章 一個經典例子讓你徹徹底底理解java回撥機制

接著在 onStart() 中進行了一些特殊性的處理,因為在 5.0 以下的版本,ProgressDialog 和 DatePickerDialog 會出現白色的邊框,這使得使用者體驗非常不好,所以我們要在此處進行相應的處理

最後便是封裝我們的建構函式
newInstance(OnCallDialog call, boolean cancelable, OnDialogCancelListener cancelListener),當我們要使用這個 CommonDialogFragment 的時候,先 new 一個 OnCallDialog,將我們想要顯示的 Dialog 傳進去,cancelable,用於設定對話方塊是否能被取消,可以看到在 onCancel() 有這樣一段程式碼

if(mCancelListener != null){
  mCancelListener.onCancel();
}複製程式碼

這便是我們在建構函式中傳入 OnCancelListener 的原因,當我們想要做一些取消對話方塊後的處理時,只要在建構函式中傳入 OnCancelListener,實現 onCancel() 方法就行了

三、實現各種型別的 DialogFragment

既然前面我們建立了 CommonFragment 作為所有 DialogFragment 的基類,那麼接下來我們當然要好好地來實現各種型別的 DialogFragment 了,我的思路是建立一個 DialogFragmentHelper 作為實現提示框的幫助類,幫我們把程式碼都封裝起來,使用的時候只需要關注與 AlertDialog 的互動,Helper 會幫助我們用 DialogFragment 來進行顯示,這樣既能統一整個應用的 Dialog 風格,又能讓我們實現各種各樣的對話方塊變得相當的簡單

在實現 DialogFragmentHelper 之前我們有兩件事先要做一下

1、在 styles 檔案中定義我們定義我們對話方塊的風格樣式
<style name="Base_AlertDialog" parent="Base.Theme.AppCompat.Light.Dialog">

        <!--不設定在6.0以上會出現,寬度不充滿螢幕的情況-->
        <item name="windowMinWidthMinor">90%</item>

        <!-- 取消標題欄,如果在程式碼中settitle的話會無效 -->
        <item name="android:windowNoTitle">true</item>

        <!-- 標題的和Message的文字顏色 -->
        <!--<item name="android:textColorPrimary">@color/black</item>-->

        <!-- 修改頂部標題背景顏色,具體顏色自己定,可以是圖片 -->
        <item name="android:topDark">@color/app_main_color_deep</item>

        <!--<item name="android:background">@color/white</item>-->

        <!-- 在某些系統上面設定背景顏色之後出現奇怪的背景,處這裡設定背景為透明,為了隱藏邊框 -->
        <!--<item name="android:windowBackground">@android:color/transparent</item>-->
        <!--<item name="android:windowFrame">@null</item>-->

        <!-- 進入和退出動畫,左進右出(系統自帶) -->
        <!--<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>-->

        <!-- 按鈕字型顏色,全部一起改,單個改需要在Java程式碼中修改 -->
        <item name="colorAccent">@color/app_main_color</item>
    </style>複製程式碼

我已經打上了詳細的註釋,相信應該很容易理解

2、寫一個介面,用於 DialogFragmentHelper 與邏輯層之間進行資料監聽
public interface IDialogResultListener<T> {
    void onDataResult(T result);
}複製程式碼

準備工作做完了,就讓我們開工擼 DialogFragmentHelper 吧,因為篇幅有限,我只是代表性的選了其中的一些效果來講,具體的程式碼,可以參考下 DialogFragmentDemos

public class DialogFragmentHelper {

    private static final String TAG_HEAD = DialogFragmentHelper.class.getSimpleName();

    /**
     * 載入中的彈出窗
     */
    private static final int PROGRESS_THEME = R.style.Base_AlertDialog;
    private static final String PROGRESS_TAG = TAG_HEAD + ":progress";


    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, String message){
        return showProgress(fragmentManager, message, true, null);
    }

    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, String message, boolean cancelable){
        return showProgress(fragmentManager, message, cancelable, null);
    }

    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, final String message, boolean cancelable
            , CommonDialogFragment.OnDialogCancelListener cancelListener){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                ProgressDialog progressDialog = new ProgressDialog(context, PROGRESS_THEME);
                progressDialog.setMessage(message);
                return progressDialog;
            }
        }, cancelable, cancelListener);
        dialogFragment.show(fragmentManager, PROGRESS_TAG);
        return dialogFragment;
    }

    /**
     * 帶輸入框的彈出窗
     */
    private static final int INSERT_THEME = R.style.Base_AlertDialog;
    private static final String INSERT_TAG  = TAG_HEAD + ":insert";

    public static void showInsertDialog(FragmentManager manager, final String title, final IDialogResultListener<String> resultListener, final boolean cancelable){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                // ...
                AlertDialog.Builder builder = new AlertDialog.Builder(context, INSERT_THEME);
                builder.setPositiveButton(DIALOG_POSITIVE, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if(resultListener != null){
                            resultListener.onDataResult(editText.getText().toString());
                        }
                    }
                });
                builder.setNegativeButton(DIALOG_NEGATIVE, null);
                return builder.create();
            }
        }, cancelable, null);
        dialogFragment.show(manager, INSERT_TAG);
    }
}複製程式碼

可以看到因為我們實現封裝了 CommonFragment,所有這些效果的實現都變得相當的簡單嗎,這便是封裝給我們帶來的便利和好處。

就以 載入中的彈出窗 為例,來看看我們是怎麼實現的

    public static CommonDialogFragment showProgress(FragmentManager fragmentManager, final String message, boolean cancelable
            , CommonDialogFragment.OnDialogCancelListener cancelListener){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {
            @Override
            public Dialog getDialog(Context context) {
                ProgressDialog progressDialog = new ProgressDialog(context, PROGRESS_THEME);
                progressDialog.setMessage(message);
                return progressDialog;
            }
        }, cancelable, cancelListener);
        dialogFragment.show(fragmentManager, PROGRESS_TAG);
        return dialogFragment;
    }複製程式碼

我們先呼叫了 CommonDialogFragment 的建構函式,將一個 ProgressDialog 傳進去,然後依次傳入 cancelable 和 cancelListener,最後呼叫 show() 函式,將 DialogFragment 顯示出來,因為我們使用了建構函式的過載,可以看到最簡單的建構函式只需要傳入兩個引數就行了,是不是相當的簡潔啊。

應該還沒忘了我們上面建立了一個 IDialogResultListener<T> 用於 DialogFragment 與邏輯層之間進行資料監聽吧,為了能傳入各種各樣型別的資料,這裡我使用了 泛型 來進行處理,就以 帶輸入框的彈出窗 為例來看看究竟要怎麼使用吧

    public static void showInsertDialog(FragmentManager manager, final String title, final IDialogResultListener<String> resultListener, final boolean cancelable){

        CommonDialogFragment dialogFragment = CommonDialogFragment.newInstance(new CommonDialogFragment.OnCallDialog() {

            @Override
            public Dialog getDialog(Context context) {

             // ... 這裡省略一部分程式碼
                AlertDialog.Builder builder = new AlertDialog.Builder(context, INSERT_THEME);
                builder.setPositiveButton(DIALOG_POSITIVE, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if(resultListener != null){
                            resultListener.onDataResult(editText.getText().toString());
                        }
                    }
                });
                builder.setNegativeButton(DIALOG_NEGATIVE, null);
                return builder.create();

            }
        }, cancelable, null);
        dialogFragment.show(manager, INSERT_TAG);
    }複製程式碼

可以看到我們在 showInsertDialog() 方法中傳入了IDialogResultListener<String> resultListener,當我們想要處理輸入的內容的時候,只要在外部呼叫的時候,new 一個 IDialogResultListener 傳進去,然後實現 onDataResult() 方法就行了

以上便是全文的內容,具體的程式碼以及示例我都放上 Github 了,有需要的朋友可以去看一下 DialogFragmentDemos,如果覺得對你有所幫助的話,就賞個 star 吧!


猜你喜歡

相關文章