DialogFragment使用到源碼完全解析

前言

最近項目中用到了DialogFragment阱冶,用起來很方便捌年,但是坑比較多飒筑,于是自己研究了下源碼离例,理清楚DialogFragment中Dialog和Fragment的關(guān)系咏雌,以及DialogFragment的原理凡怎。

DialogFragment的使用方法

1、重寫onCreateDialog方法創(chuàng)建AlertDialog

1.1 簡單的AlertDialog

public class FireMissilesDialogFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Use the Builder class for convenient dialog construction
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_fire_missiles)
               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // FIRE ZE MISSILES!
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       // User cancelled the dialog
                   }
               });
        // Create the AlertDialog object and return it
        return builder.create();
    }
}

1.2 自定義布局的AlertDialog

如果想讓對話框具有自定義布局赊抖,請創(chuàng)建一個布局统倒,然后通過調(diào)用 AlertDialog.Builder 對象上的 setView() 將其添加到 AlertDialog。

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    // Get the layout inflater
    LayoutInflater inflater = getActivity().getLayoutInflater();

    // Inflate and set the layout for the dialog
    // Pass null as the parent view because its going in the dialog layout
    builder.setView(inflater.inflate(R.layout.dialog_signin, null))//R.layout.dialog_sign 自定義布局
    // Add action buttons
           .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
               @Override
               public void onClick(DialogInterface dialog, int id) {
                   // sign in the user ...
               }
           })
           .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
               public void onClick(DialogInterface dialog, int id) {
                   LoginDialogFragment.this.getDialog().cancel();
               }
           });
    return builder.create();
}

1.3 DialogFragment與所在的Acitivty交互

當用戶觸摸對話框的某個操作按鈕或從列表中選擇某一項時氛雪,DialogFragment 可能會執(zhí)行必要的操作房匆,如果想將事件傳遞給打開該對話框的 Activity 或Fragment。 可以為每種點擊事件定義一種方法报亩。

public class NoticeDialogFragment extends DialogFragment {



    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // Build the dialog and set up the button click handlers
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setMessage(R.string.dialog_fire_missiles)
               .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       //將點擊Positive事件傳遞給所在的Activity
                       mListener.onDialogPositiveClick(NoticeDialogFragment.this);
                   }
               })
               .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       //將點擊Negative 事件傳遞給所在的Activity
                       mListener.onDialogNegativeClick(NoticeDialogFragment.this);
                   }
               });
        return builder.create();
    }

//定義一個監(jiān)聽的接口浴鸿,DialogFragment所在的Activity實現(xiàn)這個接口

    public interface NoticeDialogListener {
        public void onDialogPositiveClick(DialogFragment dialog);
        public void onDialogNegativeClick(DialogFragment dialog);
    }

    // Use this instance of the interface to deliver action events
    NoticeDialogListener mListener;

    // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (NoticeDialogListener) activity;
            //獲取DialogFragment所在的Activity,執(zhí)行mListener方法時會自動調(diào)用Actvity中相應(yīng)的方法
        } catch (ClassCastException e) {
           
            throw new ClassCastException(activity.toString()
                    + " must implement NoticeDialogListener");
        }
    }
    ...
}

DialgFragment所在的Acitivity如下

public class MainActivity extends FragmentActivity
                          implements NoticeDialogFragment.NoticeDialogListener{
    ...

    public void showNoticeDialog() {
        // 創(chuàng)建DialogFragment的實例來顯示
        DialogFragment dialog = new NoticeDialogFragment();
        dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
    }

//當DialogFragment中發(fā)生相應(yīng)的點擊事件時會自動調(diào)用到這里面的兩個方法。
    @Override
    public void onDialogPositiveClick(DialogFragment dialog) {
        // 用戶點擊DialogFragment中的positive按鈕
        ...
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
        // 用戶點擊DialogFragment中的 negative 按鈕 
        ...
    }

2.重寫onCreateView

有時候需要彈出框弦追,但是不需要AlertDialog里面的功能岳链,就可以重寫onCreateView實現(xiàn)自己的布局

 @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.message_share_websit_dialog, container);     
        initView(view);
        return view;
    }

3 以彈出框方式顯示對話框和全屏Fragment方式顯示對話框

有時候在大尺寸的手機或者pad上可以將DialogFragment作為彈出框形式展示,在小屏幕的手機上作為一個普通Fragment的形式展示劲件。

public class CustomDialogFragment extends DialogFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the layout to use as dialog or embedded fragment
        return inflater.inflate(R.layout.purchase_items, container, false);
    }

    /** The system calls this only when creating the layout in a dialog. */
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        // The only reason you might override this method when using onCreateView() is
        // to modify any dialog characteristics. For example, the dialog includes a
        // title by default, but your custom layout might not need it. So here you can
        // remove the dialog title, but you must call the superclass to get the Dialog.
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        return dialog;
    }
}

以下代碼可根據(jù)屏幕尺寸決定將片段顯示為對話框還是全屏 UI:

public void showDialog() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    CustomDialogFragment newFragment = new CustomDialogFragment();

    if (mIsLargeLayout) {
        // 如果時大屏幕的設(shè)備掸哑,顯示為彈出框方式
        newFragment.show(fragmentManager, "dialog");
    } else {
        // 如果是小屏幕的手機,顯示為全屏的Fragment
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        // 設(shè)置動畫效果
        transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        //獲取android.R.id.content布局零远,并將newFragment加入到布局中
        transaction.add(android.R.id.content, newFragment)
                   .addToBackStack(null).commit();
    }
}

DialogFragment中源碼分析

DialogFragment的繼承結(jié)構(gòu)

    public class DialogFragment extends Fragment
            implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener

可以看出DialogFragment繼承了Fragment是在Fragment基礎(chǔ)之上封裝的苗分,因此DialogFragment既可以作為Dialog來使用也可以作為單獨的Fragment來使用。

問題1:DialogFragment既然繼承了Fragment為什么會顯示成一個Dialog的形式牵辣?

在DialogFragment內(nèi)部定義了一個Dialog mDialog;當我們重寫了onCreateDialog()方法時摔癣,mDialog就是在onCreateDialog()中返回的Dialog,否則就會默認返回一個Dialog。如果我們重寫了onCreateView方法就將該布局加入到Dialog中。這個方法可以在onActivityCreated中找到

@Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (!mShowsDialog) {
            return;//如果不顯示為Dialog形式的話不做任何處理直接返回供填。
        }
        View view = getView();
        //獲取dialogFragment的布局拐云,這個布局就是我們在onCreateView中找到的布局。
        if (view != null) {
            if (view.getParent() != null) {
                throw new IllegalStateException(
                        "DialogFragment can not be attached to a container view");
            }
            mDialog.setContentView(view);//如果布局不為null的話近她,將我們定義的布局加入到mDialog中叉瘩。
        }
        final Activity activity = getActivity();
        if (activity != null) {
            mDialog.setOwnerActivity(activity);
        }
        mDialog.setCancelable(mCancelable);
        mDialog.setOnCancelListener(this);
        mDialog.setOnDismissListener(this);
        if (savedInstanceState != null) {
            Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
            if (dialogState != null) {
                mDialog.onRestoreInstanceState(dialogState);
            }
        }
    }

對應(yīng)的流程圖:


在這里插入圖片描述

onGetLayoutInflater重寫了Fragment中獲取顯示布局的過程,這個過程也決定了最后DialogFragment最后顯示的效果粘捎。

@Override
    public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
        if (!mShowsDialog) {
        //如果不顯示Dialog的話直接調(diào)用Fragment中的onGetLayoutInflater方法
            return super.onGetLayoutInflater(savedInstanceState);
        }
        mDialog = onCreateDialog(savedInstanceState);//否則新建dialog

        if (mDialog != null) {
            setupDialog(mDialog, mStyle);//設(shè)置Dialog的樣式

            return (LayoutInflater) mDialog.getContext().getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);//返回dialog的布局
        }
        return (LayoutInflater) mHost.getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }

創(chuàng)建Dialog薇缅,如果重寫了該方法就返回我們定義的Dialog,否則返回默認的Dialog.

  @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new Dialog(getActivity(), getTheme());
    }

流程圖如下:

在這里插入圖片描述

重點:是展示普通的Fragment還是以Dialog形式展示,由mShowsDialog來控制攒磨,mShowsDialog也可以由我們來賦值泳桦。

問題2 DialogFragment展示

DialogFragment的展示也有不同的方式,實際兩種方式本質(zhì)上是一樣的娩缰,都是需要得到FragmentTransaction來對DialogFragment的進行管理灸撰。

public void show(FragmentManager manager, String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        //獲取FragmentTransaction并將
       //當前DialogFragment的實例加入到FragmentTransaction中
        ft.add(this, tag);
        ft.commit();//提交
    }

    public int show(FragmentTransaction transaction, String tag) {
        mDismissed = false;
        mShownByMe = true;
        transaction.add(this, tag);
        mViewDestroyed = false;
        mBackStackId = transaction.commit();
        return mBackStackId;
    }

問題3 DialogFragment消失

DialogFragment的消失函數(shù)有onDismiss和dismiss兩個拼坎,都調(diào)用了dismissInternal(boolean allowStateLoss) 方法浮毯,但是傳入的參數(shù)不同;allowStateLoss代表是否允許不保存DialogFragment的狀態(tài)。
傳入自定的Dialog,調(diào)用 dismissInternal(true);不保存DialogFragment的狀態(tài)泰鸡。

    public void onDismiss(DialogInterface dialog) {
        if (!mViewDestroyed) {
            dismissInternal(true);
        }
    }

dismissInternal(false);保存DialogFragment的狀態(tài)

    @Override
    public void dismiss() {      
         dismissInternal(false);      
    }

具體的消失邏輯在dismissInternal(boolean allowStateLoss)實現(xiàn)下面具體分析源碼

void dismissInternal(boolean allowStateLoss) {
        if (mDismissed) {
            return;//如果當前的DialogFragment的已經(jīng)消失了债蓝,就直接返回
        }
        mDismissed = true;
        mShownByMe = false;
        if (mDialog != null) {
            mDialog.dismiss();//如果mDialog不為null那么想將mDialog消失
        }
        mViewDestroyed = true;
        if (mBackStackId >= 0) {//如果mBackStackId >0則將BackStack中所有的mBackStackId之前的DialogFragment都彈出棧
            getFragmentManager().popBackStack(mBackStackId,
                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
            mBackStackId = -1;
        } else {
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.remove(this);//從FragmentTransaction中移除當前的DialogFragment
            if (allowStateLoss) {
                ft.commitAllowingStateLoss();//如果允許不保存狀態(tài)執(zhí)行
            } else {
                ft.commit();//提交
            }
        }
    }

由上可知,DialogFragment的消失其實有兩步盛龄,首先看自帶的mDialog是不是null如果不是null則先將mDialog消失饰迹,然后再移除DialogFragment。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末余舶,一起剝皮案震驚了整個濱河市啊鸭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匿值,老刑警劉巖赠制,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異千扔,居然都是意外死亡憎妙,警方通過查閱死者的電腦和手機库正,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門曲楚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褥符,你說我怎么就攤上這事龙誊。” “怎么了喷楣?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵趟大,是天一觀的道長鹤树。 經(jīng)常有香客問我,道長逊朽,這世上最難降的妖魔是什么罕伯? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮叽讳,結(jié)果婚禮上追他,老公的妹妹穿的比我還像新娘。我一直安慰自己岛蚤,他們只是感情好邑狸,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涤妒,像睡著了一般单雾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上她紫,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天硅堆,我揣著相機與錄音,去河邊找鬼犁苏。 笑死硬萍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的围详。 我是一名探鬼主播朴乖,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼助赞!你這毒婦竟也來了买羞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤雹食,失蹤者是張志新(化名)和其女友劉穎畜普,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體群叶,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡吃挑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了街立。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舶衬。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赎离,靈堂內(nèi)的尸體忽然破棺而出逛犹,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布虽画,位于F島的核電站舞蔽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏码撰。R本人自食惡果不足惜渗柿,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脖岛。 院中可真熱鬧做祝,春花似錦、人聲如沸鸡岗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轩性。三九已至声登,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間揣苏,已是汗流浹背悯嗓。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卸察,地道東北人脯厨。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓坑质,卻偏偏與公主長得像合武,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子涡扼,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 目錄介紹 1.最簡單的使用方法1.1 官方建議1.2 最簡單的使用方法1.3 DialogFragment做屏幕適...
    楊充211閱讀 2,242評論 0 1
  • 前言 在Android開發(fā)中,Dialog被使用的非常頻繁,因此這里總結(jié)了一下Dialog的幾種使用方式 方式一 ...
    sweetying閱讀 2,193評論 2 35
  • 1 世界上著名的向日葵 展覽在絕世的美術(shù)館 看過它的人嘴唇微張 分不清狂熱之血流向哪 心臟需要一副單程支架 通往文...
    鋤風(fēng)少年閱讀 178評論 0 0