结论
DialogFragment 是 一个会自己弹出 Dialog 窗口的 Fragment。
- 对象身份是
Fragment,生命周期由FragmentManager驱动。 - 画面画在独立的
android.app.Dialog窗口里,不进入 Activity 的FrameLayout。 show()只提交事务。真正Dialog.show()发生在onStart。onCreate定 theme,onCreateDialog建窗口,onCreateView/onViewCreated建内容,onActivityCreated把 View 塞进 Dialog。
依据是 androidx.fragment:fragment:1.2.5 与平台 Dialog / LayoutInflater。较新 AndroidX 可能把 setContentView 从 onActivityCreated 挪走,但这条先后关系不变。
官方定义见 Display dialogs with DialogFragment:Dialog 的显示、隐藏、关闭由 Fragment API 驱动,不要直接操作 Dialog。
三个对象
日常说的「弹窗」经常把下面三件事混成一件:
| 对象 | 它是什么 | 谁管它 |
|---|---|---|
Fragment | 一段有生命周期的 UI 逻辑 | FragmentManager |
Dialog | 一个独立 Window | WindowManager |
DialogFragment | 用 Fragment 生命周期去管一个 Dialog | 两者都管 |
身份写在继承上:
open class CardDialog : DialogFragment()展示也走 Fragment API:
dialog.show(supportFragmentManager, null)this 是 Fragment。dialog 字段才是窗口。requireView() 才是卡片那棵 View 树。
左边是宿主关系:Fragment 已经 onAttach 到 Activity。右边是窗口关系:View 树要等到 setContentView 才成为 Dialog 内容区的 child。
内容挂在哪一个窗口
普通 Fragment 的 View 挂在 Activity 窗口的 container 里。DialogFragment 的 View 挂在另一扇 Dialog 窗口里。
所以 onCreateView 的 container 是 null:内容稍后由框架放进 Dialog,不塞进 Activity 布局。inflate 时根节点没有 parent,XML 上的 layout_width / layout_height 不会立刻变成 layoutParams。
这也是为什么不用纯 new Dialog(context):Dialog 不管配置变化,没有 FragmentManager 恢复,生命周期要自己对齐。DialogFragment 用 Fragment 把窗口绑到宿主 Activity 上。代价是同时出现两套回调。
生命周期
show(fm, tag) 默认 commit() 入队。返回时窗口还不存在。生命周期由 FragmentManager 稍后 execPendingActions() 驱动。
四个参与者:
| 角色 | 职责 |
|---|---|
| Activity | 调用 show(),提交事务 |
| FragmentManager | 稍后执行事务,驱动回调 |
| DialogFragment | 建 theme、窗口、内容树 |
| Dialog | 在 onStart 里 show() / addView |
DialogFragment 1.2.5 的调用点:
onGetLayoutInflater()里调用onCreateDialog(),再setupDialog()。官方注释:该方法在onCreate之后、onCreateView之前。onCreateView()正常由 Fragment 驱动,container为空。onViewCreated()在onCreateView返回之后。onActivityCreated()里mDialog.setContentView(view)。onStart()里mDialog.show(),内部走到WindowManager.addView()。
onCreateDialog 必须早于 onCreateView:框架要先有带 theme 的 Dialog Context,再用它的 LayoutInflater 造内容。
每个回调改什么
回调按时间排成一条链。改动按对象落点:窗口属性走 Dialog 回调,卡片内容走 Fragment 视图回调。
| 回调 | 改什么 | 此时有没有 View |
|---|---|---|
onCreate | setStyle(),定 Dialog theme | 没有 |
onCreateDialog | new Dialog,gravity、进场动画、是否无标题 | 没有 |
onCreateView | inflate 布局 | 正在创建 |
onViewCreated | findView、绑文案和按钮 | 有 View,根节点未必有 parent |
onActivityCreated | 框架 setContentView,业务尽量不覆写 | 根 View 挂到 Dialog |
onStart | 窗口宽高、dim、系统蒙层 | 已 setContentView,即将 Dialog.show() |
setStyle() 必须放在 onCreate。官方要求:Dialog 创建后再调没有效果。
onActivityCreated 是 这个 Fragment 的回调,不是「宿主 Activity 刚创建完」。Activity 可能早已 onResume;弹窗是后来 show() 的,这个 Fragment 的 onActivityCreated 也是后跑的。AndroidX 已把它标成 deprecated,业务改用 onViewCreated / onCreate。1.2.5 仍在这里 setContentView,读框架源码时还得认这个名字。
改动落点
先判断改的是窗口还是内容:
| 要改的东西 | 放哪 |
|---|---|
| theme、点击外部是否关闭 | onCreate + setStyle |
| gravity、进场动画 | onCreateDialog |
| 窗口宽高、dim、系统蒙层 | onStart |
| inflate 哪份 XML | onCreateView |
| 文案、按钮、ViewStub | onViewCreated |
同一页面上的浮层不一定都是 DialogFragment:
| 形态 | 载体 |
|---|---|
| 居中模态卡片 | DialogFragment |
| 贴在当前窗口的半屏面板 | PopupWindow |
| 轻提示 | Toast |
| 独立一页的 Dialog 外观 | Dialog 主题 Activity |
打开类时按继承判断:: DialogFragment() 走上面这套回调;PopupWindow 没有 onCreateView;Dialog 主题 Activity 按 Activity 生命周期看。
相关笔记
- BadTokenException:Dialog异步显示与Activity生命周期竞态
- Fragment生命周期
- Fragment
- Activity生命周期
- 梳理Activity、Fragment、View、自定义View的区别与关系
- onSaveInstanceState
- Activityfinish()
评论