Android对话框开发使用DialogFragment(基于Coroutine+RxJava)
程序员文章站
2022-05-01 19:16:37
Android对话框有多种实现方法,目前比较推荐的是DialogFragment,相对于直接使用AlertDialog来说,可以避免屏幕旋转会的消失。但是其建立在回调基础上的API使用起来并不友好。好在有RxJava、Coroutine等优秀的工具,我们可以对其进行一番改造。基于Coroutine+RxJava的改造build.gradledependencies { // 省略 implementation "org.jetbrains.kotlinx:kotlinx-coro....
Android对话框有多种实现方法,目前比较推荐的是DialogFragment
,相对于直接使用AlertDialog
来说,可以避免屏幕旋转会的消失。但是其建立在回调基础上的API使用起来并不友好。好在有RxJava、Coroutine等优秀的工具,我们可以对其进行一番改造。
基于Coroutine+RxJava的改造
build.gradle
dependencies { // 省略 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$latest_version" implementation 'io.reactivex.rxjava2:rxjava:$latest_version' implementation 'io.reactivex.rxjava2:rxkotlin:$latest_version' implementation 'io.reactivex.rxjava2:rxandroid:$latest_version' // 省略 } kotlin { experimental { coroutines "enable" } }
继承DialogFragment
class AlertDialogFragment : DialogFragment() { private val subject = SingleSubject.create<Int>() override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val listener = { _: DialogInterface, which: Int -> subject.onSuccess(which) } return AlertDialog.Builder(activity) .setTitle("Title") .setMessage("Message") .setPositiveButton("Ok", listener) .setNegativeButton("Cancel", listener) .create() } suspend fun showAsSuspendable(fm: FragmentManager, tag: String? = null) = suspendCoroutine<Int> { cont -> show(fm, tag) subject.subscribe { it -> cont.resume(it) } } }
使用
button.setOnClickListener { launch(UI) { val result = AlertDialogFragment().showAsSuspendable(supportFragmentManager) Log.d("AlertDialogFragment", "$result Clicked") } }
屏幕旋转问题
当屏幕旋转时,会返现上述设置的Listener失效,只要理解了Fragment 和 Activity 生命周期就会知道问题的原因:
- 旋转屏幕时,Activity将会被重新创建。
-
Activity临终前会在
onSaveInstanceState()
中保存 DialogFragment的状态FragmentManagerState
; -
重建后的Activity,在
onCreate()
中会根据savedInstanceState
所给予的FragmentManagerState
自动实例化DialogFragment,并且show()
出来
总结起来流程如下:
旋转屏幕-->-Activity.onSaveInstanceState()-->-Activity.onCreate()-->- DialogFragment.show()
协程的改造让DialogFragment
结果变成同步读取,但其本质上是把suspend后面的代码在编译期变成了回调,可以理解为设置了一个Listener。那问题来了,由于横竖屏导致Fragment的重建,造成Listener丢失,此时点击按钮无法再出现预期log:
Log.d("AlertDialogFragment", "$result Clicked")
对于这种情况,一般有两种解决办法:
- 重新设置Listener。特别是对于Listener包含对宿主Activity引用的情况(匿名内部类或者被Activity实现)情况下,由于Activity也重建导致闭包过期,需要更新Listener
-
将Listener通过
arguments
或savedInstanceState
进行保存后恢复
我们通过实现一个可序列化的Subject
实现第二种方法
SerializableSingleSubject
序列化的SingleSubject
,可以将其Subscriber
可以内部状态进行保存,并恢复使用
/**
* implements Serializable并增加serialVersionUID
*/ public final class SerializableSingleSubject<T> extends Single<T> implements SingleObserver<T>, Serializable { private static final long serialVersionUID = 1L; final AtomicReference<SerializableSingleSubject.SingleDisposable<T>[]> observers; @SuppressWarnings("rawtypes") static final SerializableSingleSubject.SingleDisposable[] EMPTY = new SerializableSingleSubject.SingleDisposable[0]; @SuppressWarnings("rawtypes") static final SerializableSingleSubject.SingleDisposable[] TERMINATED = new SerializableSingleSubject.SingleDisposable[0]; final AtomicBoolean once; T value; Throwable error; // 省略
AlertDialogFragment
基于SerialzableSingleSubject
,重新实现AlertDialogFragment :
class AlertDialogFragment : DialogFragment() { private var subject = SerializableSingleSubject.create<Int>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) savedInstanceState?.let { subject = it["subject"] as SerializableSingleSubject<Int> } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val listener = { _: DialogInterface, which: Int -> subject.onSuccess(which) } return AlertDialog.Builder(activity) .setTitle("Title") .setMessage("Message") .setPositiveButton("Ok", listener) .setNegativeButton("Cancel", listener) .create() } override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) outState?.putSerializable("subject", subject); } suspend fun showAsSuspendable(fm: FragmentManager, tag: String? = null) = suspendCoroutine<Int> { cont -> show(fm, tag) subject.subscribe { it -> cont.resume(it) } } }
重建后通过savedInstanceState
恢复之前的Subject
/Subscriber
,从而保证点击有效。
RxJava版本
当然,也可以脱离协程,仅使用RxJava
fun showAsSingle(fm: FragmentManager, tag: String? = null): Single<Int> { show(fm, tag) return subject.hide() }
使用时,由subscribe()
替代挂起函数的调用
button.setOnClickListener { AlertDialogFragment().showAsSingle(supportFragmentManager).subscribe { result -> Log.d("AlertDialogFragment", "$result Clicked") } }
本文地址:https://blog.csdn.net/vitaviva/article/details/107744388