前言
日常开发中,dialog是常见的功能,我们时常需要弹出来一些弹框提示用户
今天就定义了一个方便的dialog基类BaseSimpleDialogFragment,
支持快速地显示一个dialog
主要功能有:
initAnimation:设置入场和出场动画
getGravity:设置dialog显示位置(屏幕上,中,下)
getCanceledOnTouchOutside:点击空白处关闭
getWindowWidth
getWindowHeight
getPaddingLeft:动态设置宽高和间距
整体来说比较简单,也方便扩展
创建dialog的时候只需要实现BaseSimpleDialogFragment即可
比如这样:
class MyDialog: BaseSimpleDialogFragment() {
override val layoutId: Int = R.layout.dialog_my_show
companion object {
@JvmStatic
fun newInstance(): MyDialog {
val dialog = MyDialog()
dialog.arguments = Bundle().apply {
// putParcelableArrayList(DATA, data)
}
return dialog
}
}
override fun initData() {
// data = arguments?.getParcelableArrayList(DATA) ?: return
}
override fun initView(view: View) {
layoutView.findViewById<Button>(R.id.cancle).setOnClickListener {
dismissAllowingStateLoss()
}
}
}
展示的时候几行代码就可以了:
MyDialog.newInstance().apply {
//传递数据
}.show(supportFragmentManager)
源码
源码这边先贴出来:
abstract class BaseSimpleDialogFragment : DialogFragment() {
abstract val layoutId: Int
protected open fun initView(view: View) {
//sonar
}
protected open fun initData() {
//sonar
}
protected open fun initListener() {
//sonar
}
lateinit var layoutView: View
protected lateinit var mContext: Context
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initAnimation()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
layoutView = inflater.inflate(layoutId, container, false)
return layoutView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView(view)
initListener()
initData()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState).apply {
window?.run {
decorView.setPadding(
getPaddingLeft(),
getPaddingTop(),
getPaddingRight(),
getPaddingBottom()
)
val wlp = attributes.apply {
gravity = getGravity()
width = getWindowWidth()
height = getWindowHeight()
}
attributes = wlp
setWindowParam(this)
}
setCanceledOnTouchOutside(getCanceledOnTouchOutside())
}
isCancelable = getCancelable()
return dialog
}
protected open fun setWindowParam(window: Window) {
//sonar
}
protected open fun initAnimation() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setStyle(STYLE_NORMAL, R.style.FragmentDialogStyleWithAni)
} else {
setStyle(STYLE_NORMAL, R.style.FragmentDialogStyle_Low_Level_WithAni)
}
}
protected open fun getGravity(): Int {
return Gravity.CENTER
}
protected open fun getWindowWidth(): Int {
return WindowManager.LayoutParams.MATCH_PARENT
}
protected open fun getWindowHeight(): Int {
return WindowManager.LayoutParams.WRAP_CONTENT
}
protected open fun getPaddingLeft(): Int {
return 0
}
protected open fun getPaddingRight(): Int {
return 0
}
protected open fun getPaddingTop(): Int {
return 0
}
protected open fun getPaddingBottom(): Int {
return 0
}
protected open fun getCanceledOnTouchOutside(): Boolean {
return false
}
protected open fun getCancelable(): Boolean {
return true
}
override fun dismiss() {
dismissAllowingStateLoss()
}
open fun show(manager: FragmentManager) {
show(manager, javaClass.simpleName)
}
override fun show(manager: FragmentManager, tag: String?) {
try {
super.show(manager, tag)
} catch (e: Exception) {
Log.e("print", "show: $e")
}
}
}
用到的style:
<style name="FragmentDialogStyleWithAni" parent="FragmentDialogStyle">
<item name="android:windowAnimationStyle">@style/DialogAnimation</item>
</style>
<style name="FragmentDialogStyle_Low_Level_WithAni" parent="FragmentDialogStyle_Low_Level">
<item name="android:windowAnimationStyle">@style/DialogAnimation</item>
</style>
<style name="DialogAnimation" parent="@android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/push_ani_up_in</item>
<item name="android:windowExitAnimation">@anim/push_ani_down_out</item>
</style>
<style name="FragmentDialogStyle_Low_Level" parent="android:Theme.Holo.Light.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowFrame">@null</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowIsTranslucent">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="FragmentDialogStyle" parent="Base.AlertDialog.AppCompat.Light">
<!--点击窗口外是否消失-->
<item name="android:windowCloseOnTouchOutside">true</item>
<!-- 背景颜色及透明程度 -->
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 是否半透明 -->
<item name="android:windowIsTranslucent">false</item>
<!-- 是否没有标题 -->
<item name="android:windowNoTitle">true</item>
<!-- 是否浮现在activity之上 设置成false则match_parent可以全屏-->
<item name="android:windowIsFloating">true</item>
</style>
还有两个默认的进入和退出动画
<?xml version="1.0" encoding="UTF-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromYDelta="0"
android:toYDelta="100%p" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromYDelta="100%p"
android:toYDelta="0" />
</set>
内存泄漏
在使用这个自定义的dialog的时候
我发现退出页面时
LeakCanary 会在dialog dismiss后报内存泄漏
大概像这样:
代码很简单,贴出来:
class MainActivity : AppCompatActivity() {
lateinit var mydialog: MyDialog
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.showbtn).setOnClickListener {
mydialog = MyDialog.newInstance().apply {
//传递数据
}
mydialog.show(supportFragmentManager)
}
}
}
dialog内部有按钮,点击就关闭dialog:
layoutView.findViewById<Button>(R.id.cancle).setOnClickListener {
dismissAllowingStateLoss()
}
不知道大家看出来原因没有
看LeakCanary 日志,告诉我的是dialogFragment 收到了onDestroy的回调了。
也就是被销毁,那么gc就应该回收掉该fragment对象。
但是呢当前界面还持有该对象的引用造成了内存泄漏。
我们点进dismissAllowingStateLoss的源码
可以看到:dismissAllowingStateLoss应该是要去remove这个fragment,
但是如果activity持有的话,就无法被内存回收了,从而导致了内存泄漏
解决
解决办法也很简单,提供几种方法:
1:简单粗暴,dismiss的时候,把Activity的引用置位null
首先我们的kotlin代码就要改下:
var mydialog: MyDialog?=null
然后dismiss的时候,把Activity的引用置位null
mydialog=null
mydialog?.dismiss()
2:创建一个一次性的对象来使用,也就是局部变量,让当前界面不再全局持有该dialog对象。
findViewById<Button>(R.id.showbtn).setOnClickListener {
var mydialog = MyDialog.newInstance().apply {
//传递数据
}
mydialog.show(supportFragmentManager)
}
3:弱引用dialog,利用弱引用的特性,确保内存可以顺利回收
class MainActivity : AppCompatActivity() {
lateinit var mydialog: WeakReference<MyDialog>
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.showbtn).setOnClickListener {
mydialog = WeakReference(MyDialog.newInstance().apply {
//传递数据
})
mydialog.get()?.show(supportFragmentManager)
}
}
}
关于自定义DialogFragment解决内存泄漏的问题,大体就是这些办法
下面讲一下DialogFragment可能存在的一些坑
踩坑
修改dialogFragment的背景色
在onCreateView方法里通过这种方式来修改背景色
//设置dialog背景色为透明色
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
//设置dialog窗体颜色透明
dialog?.window?.setDimAmount(0f)
解决打开软键盘,dialogFragment上移问题
<!--这个属性设置为false可以防止输入法把弹窗顶上去-->
<item name="android:windowIsFloating">false</item>
打开dialogFragment,退出到后台或者打开新页面,然后再返回的时候,dialogFragment会重新执行动画
问题原因:当dialogFragment不可见的时候,会重新执行动画
解决办法:
1、在onCreateView中给windown添加转场属性。这样DialogFragment就具有了转场动画效果。
2、在onStop中取消掉转场动画,这样DialogFragment就不再有转场动画效果。此时跳转到其他页面,在回到当前dialogfragment,由于Dialogfragment动画被取消,所以不会再次执行进场动画。
3、在onResume再次为DialogFragment设置转场动画。注意这里需要使用handler延时,因为Activity是在onResume执行之后,才将自身所在的Window添加到WindowManager中的,然后才会调用ViewRootImpl的setview方法才开始View绘制的,如果不使用延时,相当于此时又给DialogFragment设置了转场动画效果。那么我们在步骤2中onStop取消动画就没有意义了。因为从其他页面回到DialogFragment执行onResume后再去绘制页面的,此时如果直接在onResume设置DialogFragment的动画,那DialogFragment实际上就具有了转场动画属性,还是会再执行一次进场动画。所以这里使用一个handler延时来规避这个时间差。(在DialogFragment渲染完成后再设置DialogFragment的转场动画,就不会对步骤2造成干扰)。此时DialogFragment具有了转场动画,那么我们结束DialogFragment时,就会有退出动画了,刚好弥补掉注意点1。
大体修改代码如下:
private val orientation: Int = R.style.BottomAnimBottom //弹出的动画
override fun onStop() {
super.onStop()
if (dialog != null && dialog?.window != null) {
dialog?.window?.setWindowAnimations(0)
}
}
private val handler: Handler = Handler()
override fun onResume() {
super.onResume()
handler.postDelayed({
if (dialog != null && dialog?.window != null) {
dialog?.window?.setWindowAnimations(orientation)
}
},500)
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (dialog != null && dialog?.window != null) {
dialog?.window?.setWindowAnimations(orientation)
}
return view
}
我们上面的例子因为用的是setStyle的方法在Style中来设置了动画效果
windowAnimationStyle属性设置为DialogAnimation
所以没有在onCreateView调用setWindowAnimations方法
但其他地方的修改是一样的,这里提供一份修改后的代码:
abstract class BaseSimpleAnimDialogFragment : BaseSimpleDialogFragment() {
private var dialogAnimation: Int = R.style.DialogAnimation //弹出的动画
private val handler: Handler = Handler()
/**
* 如果重写了initAnimation方法,也需要重写这个方法去设置入场动画
* initAnimation:用来设置弹框样式,包括了进入动画
* getdialogAnimation:只用来设置进入动画
*
* 所以如果设置的新样式里,默认动画已经改了,那么也要重写getdialogAnimation方法去修改动画
* 这样才能确保onResume方法执行时,设置的动画和样式里的动画一致
*/
protected open fun getdialogAnimation():Int {
return R.style.DialogAnimation
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dialogAnimation = getdialogAnimation()
}
override fun onResume() {
super.onResume()
handler.postDelayed({
if (dialog != null && dialog?.window != null) {
dialog?.window?.setWindowAnimations(dialogAnimation)
}
},500)
}
override fun onStop() {
super.onStop()
if (dialog != null && dialog?.window != null) {
dialog?.window?.setWindowAnimations(0)
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
}
子类需要重写initAnimation和getdialogAnimation方法来修改动画
确保initAnimation方法里面设置的Style的windowAnimationStyle属性是和getdialogAnimation返回的属性Style一致的即可文章来源:https://www.toymoban.com/news/detail-608117.html
Fragment恢复时注意事项:InstantiationException
这个问题可以参考这篇博客:
Fragment恢复时注意事项:InstantiationException,别在Fragment写有参数的构造方法
主要原因就是Fragment的构造方法内有参数需要传递
把传参的行为抽出来一个方法给外部去主动调用即可文章来源地址https://www.toymoban.com/news/detail-608117.html
到了这里,关于()自定义DialogFragment以及解决其内存泄漏问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!