Android为啥推荐用DialogFragment创建Dialog?

志不强者智不达,言不信者行不果。这篇文章主要讲述Android为啥推荐用DialogFragment创建Dialog?相关的知识,希望能为你提供帮助。
前言: 这段时候有点忙, 因为在赶项目, 说忙也都是在敷衍, 时间挤挤还是有的, 从开始写博客的那一刻起就应该一直坚持下来, 不要三天打鱼两天晒网, 上次写过一个Android进阶之( dialog详解一) , 今天继续上次的内容探究一下, 从android3.0后Android引入的DailogFragment, 我们一起从源码的角度去看看为啥谷歌推荐这样创建Dialog.
DialogFragment对话框出现的意义
为什么android系统有AlertDialog, PopupWindow对话框, 基本满足客户需求, 为啥还要跑出一个DialogFragment对话框呢?
这就要从DialogFragment的优点说起了:
有和Fragment基本一致的生命周期, 因此便于Activity更好的控制管理DialogFragment。 随屏幕旋转( 横竖屏幕切换) DialogFragment对话框随之自动调整对话框大小。而AlertDialog和PopupWindow随屏幕切换而消失。 DialogFragment的出现解决 横竖屏幕切换Dialog消失的问题。
既然是探究性的话题, 我们就从DialogFragment源码探究一下。
在此次之前先贴一张Fragment的生命周期图:

Android为啥推荐用DialogFragment创建Dialog?

文章图片

DialogFragment的全部源码我就不贴了, 要研究的童鞋就多按按Ctrl+ 左键吧(^__^) 嘻嘻……我们按照Fragment的生命周期一个一个往下看, 揭开它的神秘面纱。
首先看看onAttach( ) 方法
@ Override public void onAttach(Context context) { super.onAttach(context); if (!mShownByMe) { // 如果不是通过调用DialogFragment自己的show方法弹出Dialog的话 // 标志着这个Dialog不再被消失 mDismissed = false; } }

这个方法也没什么重要代码。
接下来看看onCreate( ) 方法
@ 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); } }

当Activity生命周期发生变换的时候, 也就是比如切换横竖屏的时候, 对Dialog的一些属性进行保存, 当再次创建Fragment的时候回到上一次Dialog的状态, 既然有取的过程, 那必定有存的过程。
@ Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mDialog != null) { Bundle dialogState = mDialog.onSaveInstanceState(); if (dialogState != null) { outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState); } } if (mStyle != STYLE_NORMAL) { outState.putInt(SAVED_STYLE, mStyle); } if (mTheme != 0) { outState.putInt(SAVED_THEME, mTheme); } if (!mCancelable) { outState.putBoolean(SAVED_CANCELABLE, mCancelable); } if (!mShowsDialog) { outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog); } if (mBackStackId != -1) { outState.putInt(SAVED_BACK_STACK_ID, mBackStackId); } }

紧接着我们看到这么一个方法:
/** @ hide */ @ Override public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { if (!mShowsDialog) { return super.getLayoutInflater(savedInstanceState); }mDialog = onCreateDialog(savedInstanceState); if (mDialog != null) { setupDialog(mDialog, mStyle); return (LayoutInflater) mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); }

这个方法虽然隐藏了, 但是我们看到, 在方法里面new了一个Dialog, onCreateDialog, 所以我们知道, 其实DialogFragment里面也是悄悄的创建了一个Dialog,创建了一个Dialog, 那系统怎么知道我们创建的Dialog的样式呢? 总要一些Dialog的初始化吧?
带着疑问, 接着我们看看onActivityCreated这个方法:
@ Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (!mShowsDialog) { return; }View view = getView(); if (view != null) { if (view.getParent() != null) { throw new IllegalStateException( " DialogFragment can not be attached to a container view" ); } mDialog.setContentView(view); } 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); } } }

我们看到有这么一段代码:
View view = getView(); if (view != null) { if (view.getParent() != null) { throw new IllegalStateException( " DialogFragment can not be attached to a container view" ); } mDialog.setContentView(view); }

看到这个我们很熟悉, 就是给Dialog设置了一个布局文件, 那么这个布局又是哪来的呢? View view = getView(); , 这里的getView获取到的值, 正是我们在onCreateView中return的那个view。那么我们在onCreateView中返回了什么, 我们Dialog就会显示啥样。
看到这里我们一定有点思路了, 如果要用DialogFragment来弹出一个Dialog, 有两种方法。
一: 重写createDialog方法, 给一个自己的Dialog
@ NonNull public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), getTheme()); }

二: 重写Fragment的onCreateView方法, 返回一个自定义个布局
@ Nullable public View onCreateView(LayoutInflater inflater, @ Nullable ViewGroup container, @ Nullable Bundle savedInstanceState) { return null; }

好了, 现在Dialog创建好了, 也初始化过了, 那么怎么显示Dialog呢?
机智的你肯定有思路了,
一: 调用DialogFragment的getDialog方法然后show
public Dialog getDialog() { return mDialog; }

二: 调用DialogFragment的show方法
public void show(FragmentManager manager, String tag)public int show(FragmentTransaction transaction, String tag)

但是这只是把一个没有UI的Fragment添加到了Activity的Fragment栈中, Dialog又是哪弹出来的呢?
我们接着Fragment的生命周期看看onStart方法, 这个方法也就是当Activity可见的时候调用。
@ Override public void onStart() { super.onStart(); if (mDialog != null) { mViewDestroyed = false; mDialog.show(); } }

我们很清晰的看到了mDialog.show(); 此时Dialog对用户可见。
再延伸一下, 我们可以看到这里有一个 if (mDialog != null)的判断, 那么什么时候mDialog= = null? 我们看看它在哪创建的:
/** @ hide */ @ Override public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { if (!mShowsDialog) { return super.getLayoutInflater(savedInstanceState); }mDialog = onCreateDialog(savedInstanceState); if (mDialog != null) { setupDialog(mDialog, mStyle); return (LayoutInflater) mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); }

也就是当mShowsDialog为false的时候, 我们的dialog就是null了。
我们再onCreate方法中看到这么一行代码:
mShowsDialog = mContainerId = = 0;

那么这个mContainerId 又是什么呢?
// When a fragment is being dynamically added to the view hierarchy, this // is the identifier of the parent container it is being added to.

英语不是很好, 大致翻译一下, 此id就是你需要将Fragment添加的那个容器, 也就是跟我们平时使用的普通的Fragment一样, 当需要把一个Fragment添加到Activity的时候, 调用
FragmentTransaction ft = manager.beginTransaction(); ft.add(R.id.container, tag);

到这, 我们终于把DialogFragment的源码跟着Fragment的生命周期跑了一遍, 总结一下:
DialogFragment也就是在内部new了一个Dialog然后显示在Activity上, 但是有了Fragment的附属后, 我们有了一套完整的生命周期了, Dialog依附Fragment从而当Activty生命周期发生变换的时候, 会重新把Fragment添加进Activity中, 从而不会使Dialog消失。
说了这么多原理性的东西, 我们都有点疲惫了, 接下来让我们来实战一下~~~~
为了展示效果, 我们还是模仿一下微信的Dialog吧, 先看看效果
Android为啥推荐用DialogFragment创建Dialog?

文章图片

普通的创建Dialog的方法, 我在Android进阶之( dialog详解一) , 已经有实现了, 如果有需要的朋友可以直接去拖代码, (^__^) 嘻嘻……
方式一: 重写DialogFragment的onCreateView
wechat_dialog.xml:
< ?xml version= " 1.0" encoding= " utf-8" ?> < RelativeLayout xmlns:android= " http://schemas.android.com/apk/res/android" android:id= " @ + id/parentPanel" android:layout_width= " match_parent" android:layout_height= " wrap_content" > < RelativeLayout android:id= " @ + id/body" android:layout_width= " match_parent" android:layout_height= " wrap_content" android:layout_centerInParent= " true" android:padding= " 10dp" > < ProgressBar android:id= " @ + id/progress" android:layout_width= " 50dp" android:layout_height= " 50dp" android:layout_marginRight= " 12dip" android:indeterminate= " false" android:indeterminateDrawable= " @ drawable/shape_ring" /> < TextView android:id= " @ + id/customFrameMsg" android:layout_width= " wrap_content" android:layout_height= " wrap_content" android:layout_centerVertical= " true" android:layout_toRightOf= " @ id/progress" android:singleLine= " true" android:text= " 正在登录..." android:textColor= " #ffffff" android:textSize= " 14.5sp" /> < /RelativeLayout> < /RelativeLayout>

布局很简单, 我们定义一个Fragment叫WechatFragment
package com.cisetech.dialogdemo.dialog; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.cisetech.dialogdemo.R; /** * author: yinqingy * date: 2016-11-01 22:20 * blog: http://blog.csdn.net/vv_bug * desc: */public class WechatFragment extends DialogFragment { @ Nullable @ Override public View onCreateView(LayoutInflater inflater, @ Nullable ViewGroup container, @ Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.wechat_dialog,null); } }

我们创建Fragment然后调用show代码:
public void showWechatDialog(){ WechatFragment wechatDialog= new WechatFragment(); wechatDialog.show(getSupportFragmentManager()," wechatDialog" ); }

然后我们运行看看效果:
Android为啥推荐用DialogFragment创建Dialog?

文章图片

好吧, Dialog是显示出来了, 出来的是这吊样, 跟我们想要的结果还很远啊,
1、去掉Dialog默认的Title位置, 然后背景设成自己的透明色。
public void showWechatDialog(){ WechatFragment wechatDialog= new WechatFragment(); wechatDialog.setStyle(DialogFragment.STYLE_NO_TITLE,R.style.loading_dialog); wechatDialog.show(getSupportFragmentManager()," wechatDialog" ); }

Dialog的style文件:
< !-- 自定义loading dialog --> < style name= " loading_dialog" parent= " android:style/Theme.Dialog" > < !--提示框是否有边框--> < item name= " android:windowFrame" > @ null< /item> < !--是否需要标题--> < item name= " android:windowNoTitle" > true< /item> < !--对话框的背景--> < item name= " android:windowBackground" > @ drawable/pd_shape< /item> < !--对话框是否悬浮--> < item name= " android:windowIsFloating" > true< /item> < !--默认Window的content背景--> < item name= " android:windowContentOverlay" > @ null< /item> < !--dialog遮罩透明度--> < item name= " android:backgroundDimAmount" > 0.5< /item> < !--是否需要dialog遮罩--> < item name= " android:backgroundDimEnabled" > true< /item> < /style>

shape文件:
< ?xml version= " 1.0" encoding= " utf-8" ?> < shape xmlns:android= " http://schemas.android.com/apk/res/android" > < corners android:radius= " 8dp" /> < solid android:color= " #7f000000" /> < /shape>

然后我们再运行下代码:
Android为啥推荐用DialogFragment创建Dialog?

文章图片

好吧, 经过我们的修改我们终于离我们的目标就差一点了, 就是Dialog的宽度了, 我们让Dialog的宽度为屏幕宽的0.618, (^__^) 嘻嘻……
重写onStart( ) 方法, 在dialog弹出之后进行修改宽度属性
@ Override public void onStart() { super.onStart(); Dialog dialog = getDialog(); //获取Dialog WindowManager.LayoutParams attr = dialog.getWindow().getAttributes(); //获取Dialog属性 WindowManager wm= (WindowManager) dialog.getContext().getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetric= new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetric); attr.width= (int) (outMetric.widthPixels*0.618f); dialog.getWindow().setAttributes(attr); }

再次运行代码:
Android为啥推荐用DialogFragment创建Dialog?

文章图片

可以看到我们已经成功达到了我们的目标, 还没完。。。
我们转换屏幕看看什么效果:
Android为啥推荐用DialogFragment创建Dialog?

文章图片

可以看到, 效果是一致的。(^__^) 嘻嘻……
我们使用直接new Dialog的形式, 然后切换横屏试试:
效果还是一致, 但是系统报了一个错误
android.view.WindowLeaked: Activity com.cisetech.dialogdemo.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{230312c5 V.E..... R....... 0,0-791,140} that was originally added here at android.view.ViewRootImpl.< init> (ViewRootImpl.java:363) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:265) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69) at android.app.Dialog.show(Dialog.java:298) at com.cisetech.dialogdemo.MainActivity.onCreate(MainActivity.java:94) at android.app.Activity.performCreate(Activity.java:5941) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2256) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2363) at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3915) at android.app.ActivityThread.access$900(ActivityThread.java:147) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1289) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5235) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)

虽然效果一样, 但是如果我们Dialog是一个输入用户名密码的Dialog, 那么当切换屏幕的时候, 用直接new Dialog的话会被清空掉, 如果是DialogFragment的话则不会被清空。
方式二, 重写onCreateDialog实现DialogFragment弹出Dialog
MainActivity.java
private WechatFragment wechatDialog; public void showWechatDialog(){ if(wechatDialog!= null& & wechatDialog.getDialog()!= null& & wechatDialog.getDialog().isShowing()){ wechatDialog.dismiss(); return; } wechatDialog= new WechatFragment(); // wechatDialog.setStyle(DialogFragment.STYLE_NO_TITLE,R.style.loading_dialog); wechatDialog.show(getSupportFragmentManager()," wechatDialog" ); }

WechatFragment.java:
@ NonNull @ Override public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = LayoutInflater.from(getActivity()); View v = inflater.inflate(R.layout.wechat_dialog, null); // 得到加载view Dialog loadingDialog = new Dialog(getActivity(), R.style.loading_dialog); // 创建自定义样式dialog loadingDialog.setCancelable(true); // 不可以用“返回键”取消 loadingDialog.setContentView(v); // 设置布局 return loadingDialog; }

运行效果:
Android为啥推荐用DialogFragment创建Dialog?

文章图片

和我们上面那种重写onCreateView的方式效果一样。
【Android为啥推荐用DialogFragment创建Dialog?】到此~ DialogFragment的基本内容已经完毕了, 以前看到一些大牛封装的DialogUtil, 接下来还会写一篇关于这个工具类的博客, 未完待续, (^__^) 嘻嘻……

    推荐阅读