目錄介紹
- 1.最簡單的使用方法
- 1.1 官方建議
- 1.2 最簡單的使用方法
- 1.3 DialogFragment做螢幕適配
- 2.原始碼分析
- 2.1 DialogFragment繼承Fragment
- 2.2 onCreate(@Nullable Bundle savedInstanceState)原始碼分析
- 2.3 setStyle(@DialogStyle int style, @StyleRes int theme)
- 2.4 onActivityCreated(Bundle savedInstanceState)原始碼分析
- 2.5 onCreateDialog(Bundle savedInstanceState)原始碼分析
- 2.6 重點分析彈窗展示和銷燬原始碼
- 3.經典總結
- 4.DialogFragment封裝庫介紹
- 5.常見問題總結
- 5.1 使用中show()方法遇到的IllegalStateException
好訊息
- 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
- 連結地址:github.com/yangchong21…
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!
- DialogFragment封裝庫專案地址:github.com/yangchong21…
1.最簡單的使用方法
1.1 官方建議
- Android比較推薦採用DialogFragment實現對話方塊,它完全能夠實現Dialog的所有需求,並且還能複用Fragment的生命週期管理,被後臺殺死後,可以恢復重建。
1.2 最簡單的使用方法
- 如下所示:
public class CustomDialogFragment extends DialogFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //設定樣式 setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.view_fragment_dialog, container, false); } public static void showDialog(FragmentActivity activity){ CustomDialogFragment customDialogFragment = new CustomDialogFragment(); customDialogFragment.show(activity.getSupportFragmentManager(),"yc"); } } //然後一行程式碼呼叫 CustomDialogFragment.showDialog(this); 複製程式碼
- 1.2.1 建立theme主題樣式,並且進行設定
- 設定樣式,以DialogFragment為例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可。
- 注意,CenterDialog中可以設定彈窗的動畫效果。
- 注意一下style常量,這裡只是展示常用的。
STYLE_NORMAL:會顯示一個普通的dialog STYLE_NO_TITLE:不帶標題的dialog STYLE_NO_FRAME:無框的dialog STYLE_NO_INPUT:無法輸入內容的dialog,即不接收輸入的焦點,而且觸控無效。 複製程式碼
- 1.2.2 重寫onCreateView方法建立彈窗
- 1.2.3 建立類的物件,然後呼叫show(FragmentManager manager, String tag)方法即可建立出彈窗
- 1.2.4 如何去掉標題欄,也許你會問,為什麼第二種要在super.onActivityCreated(savedInstanceState)之前設定呢。這個是因為,看了原始碼之後才知道onActivityCreated這個方法中,有mDialog.setContentView(view)這一步,說到setContentView是不是很熟悉。沒錯,後面再深度解析這塊原始碼思路……
//第一種 //設定樣式時,使用STYLE_NO_TITLE setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog); //第二種 @Override public void onActivityCreated(Bundle savedInstanceState) { Window window = getDialog().getWindow(); if(window!=null){ window.requestFeature(Window.FEATURE_NO_TITLE); } super.onActivityCreated(savedInstanceState); } 複製程式碼
2.原始碼分析
2.1 DialogFragment繼承Fragment
- DialogFragment是繼承Fragment,具有Fragment的生命週期,本質上說就是Fragment,只是其內部還有一個dialog而已。你既可以當它是Dialog使用,也可以把它作為Fragment使用。
2.2 onCreate(@Nullable Bundle savedInstanceState)原始碼分析
- onCreate這個方法主要是儲存一些屬性狀態,比如style樣式,theme注意,是否可以取消,後退棧的ID等等。
- 重點看一下mShowsDialog這個引數,這個引數是Boolean值,mShowsDialog = mContainerId == 0;所以,預設情況下,mContainerId就是0,所以mShowsDialog就是true;而當你在把它當成Fragment使用時,會為其指定xml佈局中位置,那麼mContainerId也會不為0,所以mShowsDialog就是false。
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mShowsDialog = mContainerId == 0; if (savedInstanceState != null) { mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL); mTheme = savedInstanceState.getInt(SAVED_THEME, 0); mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true); mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1); } } 複製程式碼
- mShowsDialog這個引數的作用
- 然後直接搜尋,可以看到這個引數,可以看到mShowsDialog是false,如果不是Dialog,則呼叫Fragment自身的方法;否則,就先建立一個dialog,然後,根據之前設定的style,通過setupDialog(mDialog, mStyle),對dialog賦值。所以,setStyle這個方法呼叫,一定要在onCreateView之前。一般來講,都會放到onCreate中呼叫。
2.3 setStyle(@DialogStyle int style, @StyleRes int theme)原始碼分析
- 這個方法很重要呢,注意是設定對話方塊的基本外觀和設定主題等等。通過手動設定Dialog和Window可以實現相同的效果,如果是在對話方塊建立之後呼叫它將會失去作用……
- 通過這個方法,可以看到,在不設定theme,即為0的情況下,theme會被設定為android.R.style.Theme_Panel。
public void setStyle(@DialogStyle int style, @StyleRes int theme) { mStyle = style; if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) { mTheme = android.R.style.Theme_Panel; } if (theme != 0) { mTheme = theme; } } 複製程式碼
2.4 onActivityCreated(Bundle savedInstanceState)原始碼分析
- 該方法的作用主要是:當DialogFragment依附的Activity被建立的時候呼叫,此時fragment的活動窗體被初始化
- 可以看到這個方法,如果是彈窗已經show出來的話,則直接return。然後通過setContentView方法將view建立出來。同時還設定了彈窗是否可以被取消,以及點選事件等等。
2.5 onCreateDialog(Bundle savedInstanceState)原始碼分析
- onCreateDialog方法,你可以重寫這個方法,建立一個自己定義好的dialog。預設情況下,會自己建立一個Dialog。
@NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), getTheme()); } 複製程式碼
2.6 重點分析彈窗展示和銷燬原始碼
2.6.1 show方法
- 第一種:顯示對話方塊,將片段新增到給定的FragmentManager中。這對於顯式建立事務、使用給定的標記將片段新增到事務並提交它是很方便的。這樣做可以將事務新增到後臺堆疊。當片段被取消時,將執行一個新的事務來從活動中刪除它。
- 第二種:顯示對話方塊,使用現有事務新增片段,然後提交事務。
- 共同點:這兩種顯示方式都是通過tag的方式將DialogFragment以事務的形式提交,不同的是第二種方式是採用已經建立過的transaction,並且他返回了一個int型別的數值mBackStackId,mBackStackId是幹什麼用的呢?
- mBackStackId:是做為將DialogFragment壓入回退棧的編號,初始值是-1,如果DialogFragment是用第二種方式show的話,他將被transaction預設壓入回退棧,mBackStackId=transaction.commit(),此時她的回退棧編號大於0,她的具體使用在dismissInternal方法中後面會具體介紹
public void show(FragmentManager manager, String tag) { mDismissed = false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); 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; } 複製程式碼
2.6.2 dismiss()銷燬方法
- 在原始碼中可以看到這兩個方法都呼叫了dismissInternal(boolean)方法,不同的是傳入的boolean值一個為false一個為true,那麼究竟這個boolean起到什麼作用呢?
- 在dismissInternal這個方法中,主要操作了:如果對話方塊已經不可見就跳出方法體;設定對話方塊消失,然後將對話方塊屬性設定不可見;如果DialogFragment中的Dialog物件不為空,就讓其內的對話方塊消失;然後銷燬View;對於回退棧編號mBackStackId,在前面show方法原始碼分析時提到這個呢!主要是用show(FragmentTransaction transaction, String tag)這個方法來壓棧的,所以要取消對話方塊需要在這裡面判斷,已壓棧的要彈出回退棧,這個回退棧是由Activity來管理的,如果show(FragmentManager manager, String tag)方式的話則不需要彈棧,只需要在FragmentTransaction中將其remove掉即可。
- 簡單總結就是:呼叫dialog的dismiss方法後,如果自己在後退棧中,就將自己從後退棧中移除掉;如果自己不在後退棧中,就將自己從FragmentManager中移除掉。
2.6.3 dialog顯示與隱藏
- 具體看下面程式碼
- 在OnStart的時候,將dialog進行show出來;在生命週期方法onStop()時,則是將其先隱藏;最後在onDestroyView方法,它會將dialog銷燬並置null。
@Override public void onStart() { super.onStart(); if (mDialog != null) { mViewDestroyed = false; mDialog.show(); } } @Override public void onStop() { super.onStop(); if (mDialog != null) { mDialog.hide(); } } @Override public void onDestroyView() { super.onDestroyView(); if (mDialog != null) { // Set removed here because this dismissal is just to hide // the dialog -- we don't want this to cause the fragment to // actually be removed. mViewDestroyed = true; mDialog.dismiss(); mDialog = null; } } 複製程式碼
3.經典總結
- DialogFragment是繼承Fragment,具有Fragment的生命週期,本質上說就是Fragment,只是其內部還有一個dialog而已。你既可以當它是Dialog使用,也可以把它作為Fragment使用。
- onCreateView可以載入客戶化更高的對話方塊,onCreateDialog載入系統AlertDialog型別對話方塊比較合適。
- DialogFragmnet對話方塊橫屏時對話方塊不會關閉,因為DailogFragment有Fragment屬性,會在螢幕發生變化時重新建立DialogFragment。
- setStyle的呼叫點,要放在onCreateView前,一般是放在onCreat方法中執行,否則,設定的style和theme將不起作用!setStyle中,style的引數是不可以相互一起使用的,只能用一個,如果還不滿足你使用,可以通過設定theme來滿足。
4.DialogFragment封裝庫介紹
專案地址:github.com/yangchong21…
- 自定義對話方塊,其中包括:自定義Toast,採用builder模式,支援設定吐司多個屬性;自定義dialog控制元件,仿IOS底部彈窗;自定義DialogFragment彈窗,支援自定義佈局,也支援填充recyclerView佈局;自定義PopupWindow彈窗,輕量級,還有自定義Snackbar等等;還有自定義loading載入窗,簡單便用。這裡只是展示dialogFragment用法!
- 第一種:鏈式程式設計,如下所示
BottomDialogFragment.create(getSupportFragmentManager()) .setViewListener(new BottomDialogFragment.ViewListener() { @Override public void bindView(View v) { } }) .setLayoutRes(R.layout.dialog_bottom_layout_list) .setDimAmount(0.5f) .setTag("BottomDialog") .setCancelOutside(true) .setHeight(getScreenHeight() / 2) .show(); 複製程式碼
- 第二種:直接繼承,可以高度定製自己想要的彈窗
public class ADialog extends BaseDialogFragment { @Override protected boolean isCancel() { return false; } @Override public int getLayoutRes() { return 0; } @Override public void bindView(View v) { } } 複製程式碼
5.常見問題總結
5.1 使用中show()方法遇到的IllegalStateException
- 報錯日誌如下:
lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1493) 複製程式碼
- 出現該問題的原因
- Activity 呼叫了onSaveInstanceState()以後有觸發了dialog的顯示,dialog.show()方法裡邊用的是commit()而不是commitAllowingStateLoss()
- 追蹤報錯日誌的來源
- 於是,我挺好奇,show方法中只有兩個引數,決定從getSupportFragmentManager()方法分析.FragmentManager是抽象類,我這裡主要是看FragmentManagerImpl實現類程式碼
//第一步: public FragmentManager getSupportFragmentManager() { return mFragments.getSupportFragmentManager(); } //第二步: public FragmentManager getSupportFragmentManager() { return mHost.getFragmentManagerImpl(); } //第三步: FragmentManagerImpl getFragmentManagerImpl() { return mFragmentManager; } //第四步:看beginTransaction()方法 @Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); } //第五步:看BackStackRecord類中看commit方法 @Override public int commit() { return commitInternal(false); } @Override public int commitAllowingStateLoss() { return commitInternal(true); } //第六步:可以看到這倆函式的區別就是commitInternal()方法中引數一個為true,一個為false int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(TAG); PrintWriter pw = new PrintWriter(logw); dump(" ", null, pw, null); pw.close(); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; } //第七步:再追蹤到enqueueAction(this,allowStateLoss) public void enqueueAction(OpGenerator action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { if (mDestroyed || mHost == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<>(); } mPendingActions.add(action); scheduleCommit(); } } //第八步:checkStateLoss()方法,這裡可以看到丟擲的錯誤日誌呢 private void checkStateLoss() { if (mStateSaved) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } if (mNoTransactionsBecause != null) { throw new IllegalStateException( "Can not perform this action inside of " + mNoTransactionsBecause); } } 複製程式碼
關於其他內容介紹
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:github.com/yangchong21…
- 知乎:www.zhihu.com/people/yang…
- 簡書:www.jianshu.com/u/b7b2c6ed9…
- csdn:my.csdn.net/m0_37700275
- 喜馬拉雅聽書:www.ximalaya.com/zhubo/71989…
- 開源中國:my.oschina.net/zbj1618/blo…
- 泡在網上的日子:www.jcodecraeer.com/member/cont…
- 郵箱:yangchong211@163.com
- 阿里雲部落格:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:segmentfault.com/u/xiangjian…