什么是DialogFragment
如果没记错DialogFragment应该是14年的时候就有的,对于不少初学者甚至到今天都是直接用Dialog而不是DialogFragment,其实也没有绝对的对错,Dialog实现简单也写得也快,DialogFrament需要的代码量也比较多,相比之下当屏幕旋转后,Activity重建了,DialogFrament能自动恢复显示,Dialog会消失,你需要手动写代码恢复Dialog的显示。
A fragment that displays a dialog window, floating on top of its activity's window. This fragment contains a Dialog object, which it displays as appropriate based on the fragment's state. Control of the dialog (deciding when to show, hide, dismiss it) should be done through the API here, not with direct calls on the dialog. 以上来自Google的原话: developer.android.com/reference/a…
个人的优化方案
一、贴代码
正如我第一篇文章,我大部分的代码都是尽量利用kotlin DSL的优势,能清晰的告诉维护人员或者使用起来能方便。
还是先贴代码,有点长:
利用注解方便配置部分参数
@Target(AnnotationTarget.CLASS)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class WindowParam(val gravity:Int = Gravity.CENTER,
val outSideCanceled:Boolean = true,val animRes :Int = -1,
val canceled:Boolean = true, val dimAmount :Float = -1f,
val fillWidth : Boolean = true,
val fillHeight :Boolean= false)
abstract class SimpleDialogFragment : DialogFragment() {
private val mDialog by lazy { Dialog(requireContext()) }
private var hasInflate = false
private var onCreate :(()->Int)? = null
private var onWindow :(Window.()->Unit)? = null
private var onView :(View.()->Unit)? = null
abstract fun build(savedInstanceState: Bundle?)
private var owner : LifecycleOwner? = null
private fun init(owner: LifecycleOwner){
this.owner = owner
}
override fun onStop() {
super.onStop()
dismissAllowingStateLoss()
}
override fun onDestroy() {
super.onDestroy()
dismissAllowingStateLoss()
}
fun buildDialog(onCreate :(()->Int)) : SimpleDialogFragment {
this.onCreate = onCreate
return this
}
fun onWindow(onWindow :(Window.()->Unit)) : SimpleDialogFragment {
this.onWindow = onWindow
return this
}
fun <T : ViewDataBinding> View.onBindingView(onBindingView :(T.()->Unit)){
onBindingView.invoke(DataBindingUtil.bind<T>(this)!!)
}
fun onView(onView :(View.()->Unit)) : SimpleDialogFragment {
this.onView = onView
return this
}
override fun onAttach(context: Context) {
super.onAttach(context)
if(context is AppCompatActivity){
init(context)
}else if(context is LifecycleOwner){
init(context)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
if(hasInflate) return mDialog
build(savedInstanceState)
val viewId = onCreate?.invoke()
if(viewId!= null){
val view = View.inflate(activity, viewId, null)
val param = javaClass.getAnnotation(WindowParam::class.java)!!
val gravity = param.gravity
val outSideCanceled = param.outSideCanceled
val canceled = param.canceled
val dimAmount = param.dimAmount
val animRes = param.animRes
val fillWidth = param.fillWidth
val fillHeight = param.fillHeight
mDialog.setContentView(view)
mDialog.setCanceledOnTouchOutside(outSideCanceled)
mDialog.setCancelable(canceled)
val window = mDialog.window
val dm = DisplayMetrics()
window?.apply {
window.statusBarColor = Color.TRANSPARENT
windowManager.defaultDisplay.getMetrics(dm)
val layoutHeight = if(fillWidth){
dm.widthPixels
}else{
window.attributes.width
}
val layoutWidth = if(fillHeight){
dm.heightPixels
}else{
window.attributes.height
}
setLayout(layoutHeight, layoutWidth)
setBackgroundDrawable(ColorDrawable(0x00000000))
setGravity(gravity)
if(animRes!=-1){
setWindowAnimations(animRes)
}
if(dimAmount!=-1f){
setDimAmount(dimAmount)
}
onWindow?.invoke(this)
}
onView?.invoke(view)
hasInflate = true
}
return mDialog
}
override fun dismiss() {
dialog?.apply {
if(isShowing){
super.dismiss()
}
}
}
override fun show(manager: FragmentManager, tag: String?) {
try {
if(!isAdded){
val transaction = manager.beginTransaction()
transaction.add(this, tag)
transaction.commitAllowingStateLoss()
transaction.show(this)
}
}catch (e: Exception){
Log.e("DialogFragment","${e.message}")
}
}
}
例子:
@WindowParam
class TextDialog : SimpleDialogFragment() {
private var num = 0
override fun build(savedInstanceState: Bundle?) {
buildDialog {
R.layout.dialog_text
}
onView {
tv.text = "$num"
tv.setOnClickListener {
num++
tv.text = "$num"
}
}
}
}
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/colorAccent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:background="@color/colorPrimaryDark"
android:layout_width="match_parent"
android:layout_height="200dp">
<TextView
android:id="@+id/tv"
android:text="1"
android:textSize="45dp"
android:layout_centerInParent="true"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</FrameLayout>
二、逐点分析
在WindowParam里面,
val gravity:Int = Gravity.CENTER
val outSideCanceled:Boolean = true
val animRes :Int = -1,
val canceled:Boolean = true
val dimAmount :Float = -1f,
分别是对应:
dialog.window.setGravity(gravity)
dialog.setCanceledOnTouchOutside(outSideCanceled)
dialog.window.setWindowAnimations(animRes)
dialog.setCancelable(canceled)
dialog.window.setDimAmount(dimAmount) //简单理解就是背景灰色那个蒙版有多“黑”
而比较特殊的是:
val fillWidth = param.fillWidth
val fillHeight = param.fillHeight
val layoutHeight = if(fillWidth){
dm.widthPixels
}else{
window.attributes.width
}
val layoutWidth = if(fillHeight){
dm.heightPixels
}else{
window.attributes.height
}
dialog.window.setLayout(layoutHeight, layoutWidth)
fillWidth = true,fillHeight = true
fillWidth = false, fillHeight = true
fillWidth = false, fillHeight = false
由于我个人代码的习惯都会横向铺满的,竖向用wrap_content去适应的,因为大部分情况下,适配横向都是很好适配的。 当然这是我的方案,DialogFragment的宽高这个坑,百度谷歌一大堆方案,我就不在这里指出了。
buildDialog {
// 传入你的layout_id
}
onView {
// 处理逻辑
onBindingView<DialogTextBinding> {
// 当你需要用到Databinding在这里面写,this是DialogTextBinding
}
}
总结
DialogFrament我个人的处理方案就分享到这里,诸如DialogFrament的内存泄露等等这些坑,我给的建议就是不要搞那么复杂的方法了,试试让把dialog在Activity onDestroy那里把他弄成null,然后观察内存的变动,看看到底泄露多少,这样处理leakcanary在你Dialog消失后依然报警,但是你主要还是要对付那种在Activity销毁有的内存泄漏,而不是单纯为了让这个警告消失。同时记得当你在DialogFragment里有动画时候,需要及时在onStop的地方释放。