前言
很多人抱怨原生的Dialog
不好用,自定义方式比较复杂,这里介绍一下如何使用官方推荐的DialogFragment
使用方法
首先要意识到DialogFragment
的本质就是一个Fragment
,实现DialogInterface
,能够为开发者提供更多便捷的操作。
- 初始化
和Fragment
相同,重写onCreateView
加载布局
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.custom_layout, container, false)
}
其中,R.layou.custom_layout
是自己定义的布局文件
- 显示与隐藏
和Dialog
一样,使用show()
方法显示,但是在DialogFragment
中,show()
方法需要传入一个FragmentManager
保证DialogFragment
能够被正确的添加到Activity
中。在使用show()
方法时,建议重写该方法并判断isAdded
dialogFragment.show(supportFragmentManager) // In Activity
dialogFragment.show(parentFragmentManager) // In Fragment
隐藏DialogFragment
,如果使用dismiss()
方法会导致某些不可预期的错误,很多网友也分析过,建议使用dismissAllowingStateLoss()
替代
默认背景
系统自带的Dialog
是有背景的,通常自定义的时候会严重影响美观,需要加上这两句话用来去掉默认的背景:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
如果想调整背景阴影的透明度,使用dimAmount
,数值从0.0 ~ 1.0,数字越小越透明,默认值为0.6
override fun onResume() {
dialog?.window?.attributes?.dimAmount = 0.6f
}
全屏显示
网上查阅了很多资料,大部分都说要使用style
的方式,效果很不错,但是我测试的时候,dimAmount
和动画无法使用了,我目前使用以下方法解决全屏的问题,虽然不够完美,但目前来看是最优的方式:
override fun onResume() {
super.onResume()
dialog?.window?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
UI刷新
这是一个令人头疼的问题,Fragment
创建的时候我们并不知道UI什么时候能绘制完成,如果在创建DialogFragment
实例就传参刷新UI,大概率会导致崩溃,既然本质是Fragment
,就可以按照对付Fragment
的思路去对付DialogFragment
- 创建一个抽象类,继承
DialogFragment
abstract class BaseDialog : DialogFragment{ }
- 使用LiveData,绑定生命周期
val dialogData = MutableLiveData<Any?>()
- 创建一个供外部调用的方法,传入我们需要的数据
fun updateData(data: Any?) {
dialogData.postValue(data)
}
- 创建数据回调(Listener方式)
private var dataListener: ((Any?) -> Unit)? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?){
super.onViewCreated(view, savedInstanceState)
dialogData.observe(viewLifecycleOwner) { dataListener?.invoke(it) }
}
fun setOnDataListener(listener: (Any?) -> Unit) {
this.dataListener = listener
}
//子类中调用监听
setOnDataListener { data -> // TODO 根据data的数据类型执行不同的UI逻辑 }
- 创建数据回调(Abstract方式)
abstract fun onUpdate(data: Any?)
override fun onViewCreated(view: View, savedInstanceState: Bundle?){
super.onViewCreated(view, savedInstanceState)
dialogData.observe(viewLifecycleOwner) { onUpdate?.invoke(it) }
}
//在子类中重写onUpdate方法
override fun onUpdate(data:Any?){
//TODO 根据data的数据类型执行不同的UI逻辑
}
小结
DialogFragment
乍一看使用起来挺麻烦,但其本质就是一个Fragment
,我们基本可以使用对待Fragment
的思路去处理问题。
本人能力有限,学识浅薄,只能描述自己遇到的一些问题,如果有写的不好的地方欢迎批评。
补充
注意一点:如果要在DialogFragment
中嵌套一个Fragment
,需要使用childFragmentManager
来添加新的Fragment
工作中要涉及到万恶的旧版本(Api <= 22),设置全屏会显示状态栏,需要修改View的属性:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
val window = dialog?.window
if (window != null) {
val uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
dialog?.window?.decorView?.systemUiVisibility = uiOptions
}
}
如果是使用 Api30及其以上,这里会出现很多@Deprecated
删除线,不要紧张,新的Api只能在新版中使用,这里可能Studio没有处理。用以上方式可以消除状态栏,但是,弹出dialog的时候,状态栏会闪一下马上消失,暂时没有更完美的解决方法,如果有请留言,感激不尽。