在做需求的时候,有要做一个 Dialog,于是我就使用了 DialogFragment 来实现的。
但是在做的时候就遇到了一个问题,那就是要显示的 Dialog 的尺寸比较大,如果我直接在其引用的布局文件中的根节点设置指定的数值较大的宽高,但是实际上显示出来的尺寸并不是设置的大小,而且根节点好像没有生效,比如在根节点设置了指定的背景色,把 DialogFragment 显示出来的时候并没有达到预期效果。
如下:
class TestDialogFragment : AppCompatDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_test_layout, container, true)
}
}
以及引用的布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_gravity="center"
android:orientation="vertical"
android:gravity="center"
android:background="#50B61010"
android:layout_width="match_parent"
android:layout_height="500dp">
<TextView
android:background="#3070E7"
android:textSize="20dp"
android:gravity="center"
android:text="Test Dialog"
android:layout_width="600dp"
android:layout_height="400dp"/>
</RelativeLayout>
可以看到根节点是一个 RelativeLayout,并且设置了背景色(暗红色),然后内部有一个 TextView,蓝色,且指定的宽高比较大。然后看下实际的运行效果:
TestDialogFragment().show(supportFragmentManager, "TestDialogFragment")
可以看到,那个显示的 TextView 的尺寸不是预期设置的,而且它的根节点,即设置了暗红色的父布局也没有显示出来,而且在 TextView 上方还有一块白色部分,这个是 Title 布局部分,默认是会存在的。
因为暂时没有查阅具体的源码,无法从原理上解释具体的原因,但是猜测是因为系统会对布局进行调整,把它限定在一个范围内。
这个时候,如果碰到需求要求设置的比较大的尺寸,而且要按照其值显示出来,这个时候又要怎么去处理呢。
我接触到的一个方法就是在 DialogFragment 的 onCreate() 方法中设置一个指定的特殊的 style:
setStyle(AppCompatDialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Black_NoTitleBar)
这个时候,就不会有前面说的那个限制了,而且由于设置了 R.style.Theme_Black_NoTitleBar
,因此那个白色的 Title 部分也会去除了。
不过,如果只是这么处理,会产生一个额外的问题:
可以看到,虽然布局文件中设置的根节点对应的背景色显示出来了,但是根节点设置的高度是没有生效的,其 layout_height
为 500dp,但是却跟 match_parent
的效果一样,充满了屏幕。
因此,我进一步的处理方法就是设置一个 BaseDialogFragment,在其 onCreateView() 中返回额外的一个 ViewGroup,而不是直接返回布局文件中的根元素,然后再把这个根元素添加到 ViewGroup 中,从而使得布局文件根元素设置的属性能够正常的生效。
abstract class BaseDialogFragment : AppCompatDialogFragment() {
private lateinit var mView: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(AppCompatDialogFragment.STYLE_NO_FRAME, android.R.style.Theme_Black_NoTitleBar)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val layout = FrameLayout(context)
layout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
layout.setOnClickListener {
if (isBcgCanCancel()) {
dismiss()
}
}
// <color name="transparent_black_60_percent">#99000000</color>
// 参考系统默认的设置底部黑色半透明区域
dialog?.window?.setBackgroundDrawable(
ColorDrawable(context!!.resources.getColor(R.color.transparent_black_60_percent)))
mView = inflater?.inflate(getCreateViewId(), layout, false)
mView.setOnClickListener {
// 防止点击非按钮部分区域而关闭弹框
// 因为 mView 没有消耗事件的话,事件会经由前面的 layout 来处理
}
layout.addView(mView)
return layout
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isCancelable = isBackBtCanCancel()
initViews()
}
open fun initViews() {}
// 封装了以下 findViewById(),用于从布局文件中获取对应 id 的子控件
fun <T : View> getChildView(@IdRes id: Int): T {
return mView.findViewById(id)
}
// 用于设置物理的返回按键是否能够响应
open fun isBackBtCanCancel() = true
open fun isBcgCanCancel() = true
@LayoutRes
abstract fun getCreateViewId(): Int
}
大致的逻辑就入上述代码,实际的类只需要继承 BaseDialogFragment,并实现 getCreateViewId()
方法返回对应的布局 id 即可。
需要注意的就是一些细节的处理了,比如防止布局文件中的非按钮部分会导致 DialogFragment 自己关闭(因为对原始的根 FrameLayout 设置了点击事件,用于模仿正常的 Dialog 点击外部黑色透明区域而自动关闭,而如果布局文件中的根节点没有设置点击事件的话,事件实际上是会被 原始的根 FrameLayout 处理的。)
class TestDialogFragment : BaseDialogFragment() {
override fun getCreateViewId() = R.layout.dialog_test_layout
}
可以看到,是预期的效果了。