目錄介紹
- 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.經(jīng)典總結(jié)
- 4.DialogFragment封裝庫介紹
- 5.常見問題總結(jié)
- 5.1 使用中show()方法遇到的IllegalStateException
好消息
- 博客筆記大匯總【16年3月到至今】枪眉,包括Java基礎(chǔ)及深入知識點,Android技術(shù)博客再层,Python學習筆記等等贸铜,還包括平時開發(fā)中遇到的bug匯總,當然也在工作之余收集了大量的面試題聂受,長期更新維護并且修正蒿秦,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客蛋济,從12年起棍鳖,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處碗旅,謝謝渡处!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下祟辟,謝謝医瘫!當然也歡迎提出建議,萬事起于忽微旧困,量變引起質(zhì)變醇份!
- DialogFragment封裝庫項目地址:https://github.com/yangchong211/YCDialog
-
02.Toast源碼深度分析
- 最簡單的創(chuàng)建稼锅,簡單改造避免重復(fù)創(chuàng)建,show()方法源碼分析僚纷,scheduleTimeoutLocked吐司如何自動銷毀的缰贝,TN類中的消息機制是如何執(zhí)行的,普通應(yīng)用的Toast顯示數(shù)量是有限制的畔濒,用代碼解釋為何Activity銷毀后Toast仍會顯示,Toast偶爾報錯Unable to add window是如何產(chǎn)生的锣咒,Toast運行在子線程問題侵状,Toast如何添加系統(tǒng)窗口的權(quán)限等等
-
03.DialogFragment源碼分析
- 最簡單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析毅整,重點分析彈窗展示和銷毀源碼趣兄,使用中show()方法遇到的IllegalStateException分析
-
05.PopupWindow源碼分析
- 顯示PopupWindow,注意問題寬和高屬性悼嫉,showAsDropDown()源碼艇潭,dismiss()源碼分析,PopupWindow和Dialog有什么區(qū)別戏蔑?為何彈窗點擊一下就dismiss呢蹋凝?
-
06.Snackbar源碼分析
- 最簡單的創(chuàng)建,Snackbar的make方法源碼分析总棵,Snackbar的show顯示與點擊消失源碼分析鳍寂,顯示和隱藏中動畫源碼分析,Snackbar的設(shè)計思路情龄,為什么Snackbar總是顯示在最下面
-
07.彈窗常見問題
- DialogFragment使用中show()方法遇到的IllegalStateException,什么常見產(chǎn)生的迄汛?Toast偶爾報錯Unable to add window,Toast運行在子線程導(dǎo)致崩潰如何解決骤视?
1.最簡單的使用方法
1.1 官方建議
- Android比較推薦采用DialogFragment實現(xiàn)對話框鞍爱,它完全能夠?qū)崿F(xiàn)Dialog的所有需求,并且還能復(fù)用Fragment的生命周期管理专酗,被后臺殺死后睹逃,可以恢復(fù)重建。
1.2 最簡單的使用方法
- 如下所示:
public class CustomDialogFragment extends DialogFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //設(shè)置樣式 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"); } } //然后一行代碼調(diào)用 CustomDialogFragment.showDialog(this);
- 1.2.1 創(chuàng)建theme主題樣式祷肯,并且進行設(shè)置
- 設(shè)置樣式唯卖,以DialogFragment為例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可躬柬。
- 注意拜轨,CenterDialog中可以設(shè)置彈窗的動畫效果。
- 注意一下style常量允青,這里只是展示常用的橄碾。
STYLE_NORMAL:會顯示一個普通的dialog STYLE_NO_TITLE:不帶標題的dialog STYLE_NO_FRAME:無框的dialog STYLE_NO_INPUT:無法輸入內(nèi)容的dialog卵沉,即不接收輸入的焦點,而且觸摸無效法牲。
- 1.2.2 重寫onCreateView方法創(chuàng)建彈窗
- 1.2.3 創(chuàng)建類的對象史汗,然后調(diào)用show(FragmentManager manager, String tag)方法即可創(chuàng)建出彈窗
- 1.2.4 如何去掉標題欄,也許你會問拒垃,為什么第二種要在super.onActivityCreated(savedInstanceState)之前設(shè)置呢停撞。這個是因為,看了源碼之后才知道onActivityCreated這個方法中悼瓮,有mDialog.setContentView(view)這一步戈毒,說到setContentView是不是很熟悉。沒錯横堡,后面再深度解析這塊源碼思路……
//第一種 //設(shè)置樣式時埋市,使用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的生命周期命贴,本質(zhì)上說就是Fragment道宅,只是其內(nèi)部還有一個dialog而已。你既可以當它是Dialog使用胸蛛,也可以把它作為Fragment使用污茵。
2.2 onCreate(@Nullable Bundle savedInstanceState)源碼分析
- onCreate這個方法主要是保存一些屬性狀態(tài),比如style樣式葬项,theme注意省咨,是否可以取消,后退棧的ID等等玷室。
- 重點看一下mShowsDialog這個參數(shù)零蓉,這個參數(shù)是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這個參數(shù)的作用
- 然后直接搜索,可以看到這個參數(shù)部蛇,可以看到mShowsDialog是false摊唇,如果不是Dialog,則調(diào)用Fragment自身的方法涯鲁;否則巷查,就先創(chuàng)建一個dialog有序,然后,根據(jù)之前設(shè)置的style岛请,通過setupDialog(mDialog, mStyle)旭寿,對dialog賦值。所以崇败,setStyle這個方法調(diào)用盅称,一定要在onCreateView之前。一般來講后室,都會放到onCreate中調(diào)用缩膝。
- image
2.3 setStyle(@DialogStyle int style, @StyleRes int theme)源碼分析
- 這個方法很重要呢,注意是設(shè)置對話框的基本外觀和設(shè)置主題等等咧擂。通過手動設(shè)置Dialog和Window可以實現(xiàn)相同的效果,如果是在對話框創(chuàng)建之后調(diào)用它將會失去作用……
- 通過這個方法檀蹋,可以看到松申,在不設(shè)置theme,即為0的情況下俯逾,theme會被設(shè)置為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被創(chuàng)建的時候調(diào)用,此時fragment的活動窗體被初始化
- 可以看到這個方法,如果是彈窗已經(jīng)show出來的話桌肴,則直接return皇筛。然后通過setContentView方法將view創(chuàng)建出來。同時還設(shè)置了彈窗是否可以被取消坠七,以及點擊事件等等水醋。
- image
2.5 onCreateDialog(Bundle savedInstanceState)源碼分析
- onCreateDialog方法,你可以重寫這個方法彪置,創(chuàng)建一個自己定義好的dialog拄踪。默認情況下,會自己創(chuàng)建一個Dialog拳魁。
@NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), getTheme()); }
2.6 重點分析彈窗展示和銷毀源碼
2.6.1 show方法
- 第一種:顯示對話框惶桐,將片段添加到給定的FragmentManager中。這對于顯式創(chuàng)建事務(wù)潘懊、使用給定的標記將片段添加到事務(wù)并提交它是很方便的姚糊。這樣做可以將事務(wù)添加到后臺堆棧。當片段被取消時授舟,將執(zhí)行一個新的事務(wù)來從活動中刪除它救恨。
- 第二種:顯示對話框,使用現(xiàn)有事務(wù)添加片段释树,然后提交事務(wù)忿薇。
- 共同點:這兩種顯示方式都是通過tag的方式將DialogFragment以事務(wù)的形式提交,不同的是第二種方式是采用已經(jīng)創(chuàng)建過的transaction,并且他返回了一個int類型的數(shù)值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()銷毀方法
- 在源碼中可以看到這兩個方法都調(diào)用了dismissInternal(boolean)方法,不同的是傳入的boolean值一個為false一個為true,那么究竟這個boolean起到什么作用呢裙椭?
- 在dismissInternal這個方法中,主要操作了:如果對話框已經(jīng)不可見就跳出方法體署浩;設(shè)置對話框消失揉燃,然后將對話框?qū)傩栽O(shè)置不可見;如果DialogFragment中的Dialog對象不為空,就讓其內(nèi)的對話框消失筋栋;然后銷毀View炊汤;對于回退棧編號mBackStackId,在前面show方法源碼分析時提到這個呢!主要是用show(FragmentTransaction transaction, String tag)這個方法來壓棧的,所以要取消對話框需要在這里面判斷,已壓棧的要彈出回退棧,這個回退棧是由Activity來管理的,如果show(FragmentManager manager, String tag)方式的話則不需要彈棧,只需要在FragmentTransaction中將其remove掉即可弊攘。
- 簡單總結(jié)就是:調(diào)用dialog的dismiss方法后抢腐,如果自己在后退棧中,就將自己從后退棧中移除掉襟交;如果自己不在后退棧中迈倍,就將自己從FragmentManager中移除掉。
- image
- image
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.經(jīng)典總結(jié)
- DialogFragment是繼承Fragment贞言,具有Fragment的生命周期斜棚,本質(zhì)上說就是Fragment,只是其內(nèi)部還有一個dialog而已该窗。你既可以當它是Dialog使用弟蚀,也可以把它作為Fragment使用。
- onCreateView可以加載客戶化更高的對話框酗失,onCreateDialog加載系統(tǒng)AlertDialog類型對話框比較合適智厌。
- DialogFragmnet對話框橫屏時對話框不會關(guān)閉套鹅,因為DailogFragment有Fragment屬性,會在屏幕發(fā)生變化時重新創(chuàng)建DialogFragment。
- setStyle的調(diào)用點萌腿,要放在onCreateView前旨椒,一般是放在onCreat方法中執(zhí)行腌乡,否則盛龄,設(shè)置的style和theme將不起作用!setStyle中序调,style的參數(shù)是不可以相互一起使用的醉锅,只能用一個,如果還不滿足你使用发绢,可以通過設(shè)置theme來滿足硬耍。
4.DialogFragment封裝庫介紹
項目地址:https://github.com/yangchong211/YCDialog
- 自定義對話框垄琐,其中包括:自定義Toast,采用builder模式经柴,支持設(shè)置吐司多個屬性狸窘;自定義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.常見問題總結(jié)
5.1 使用中show()方法遇到的IllegalStateException
- 報錯日志如下:
lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1493)
- 出現(xiàn)該問題的原因
- Activity 調(diào)用了onSaveInstanceState()以后有觸發(fā)了dialog的顯示燃观,dialog.show()方法里邊用的是commit()而不是commitAllowingStateLoss()
- 追蹤報錯日志的來源
- 于是褒脯,我挺好奇便瑟,show方法中只有兩個參數(shù)缆毁,決定從getSupportFragmentManager()方法分析.FragmentManager是抽象類,我這里主要是看FragmentManagerImpl實現(xiàn)類代碼
//第一步: 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); } //第六步:可以看到這倆函數(shù)的區(qū)別就是commitInternal()方法中參數(shù)一個為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); } }
關(guān)于其他內(nèi)容介紹
01.關(guān)于博客匯總鏈接
- 1.技術(shù)博客匯總
- 2.開源項目匯總
- 3.生活博客匯總
- 4.喜馬拉雅音頻匯總
- 5.其他匯總
02.關(guān)于我的博客
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 簡書:http://www.reibang.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網(wǎng)上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles