Android Window浮窗UI组件使用JetPack

目前接手的一个业务,应用不是用Activity/Fragment作为界面组件,而是用Window浮窗的形式显示,并且浮窗有很多种类型,每一种类型对应一类业务。那么怎么使用Jatpack的相关特性来设计架构并提高开发效率呢?分下面几个模块做分析:

ViewModel

通常在使用ViewModel的时候,是跟Activity/Fragment对应起来的,分别实例化一个对象,这个时候,ViewModel是存在Activity的ViewModelStore里面的,而且会监听此Activity的生命周期,适时销毁。

但是在没有Activity的情况下,并且数据需要全局监听的时候,可以创建一个全局的ViewModel,设计成单例模式的类,它的生命周期是伴随应用的整个生命周期,如下:

java 复制代码
object MainViewModel : ViewModel() {
	/**
     * 语音唤醒的状态
     */
    var mWakeUp = MutableLiveData<Int>()
}

其实在这里,已经可以不用去实现ViewModel这个类了,定义一个普通的单例模式类就行了:

java 复制代码
object MainViewModel {
	/**
     * 语音唤醒的状态
     */
    var mWakeUp = MutableLiveData<Int>()
}

LiveData

MainViewModel中的LiveData主要分为两大类,一类是全局的数据监听,一类是针对于特定浮窗中使用到的数据。

第一类全局数据,可以使用全局监听,它的数据监听在应用整个生命周期都会有效:

java 复制代码
// 全局检测,不带LifeCycleOwner
        MainViewModel.mWakeUp.observeForever {
            Log.d(TAG, "wakeUp change $it")
            refreshWindowShown()
        }

第二类数据就是对应显示浮窗的数据,它应该是需要带生命周期的,即在弹窗显示的时候才更新数据,而在弹窗消失后,不用再更新数据,避免资源的浪费和内存的泄漏:

java 复制代码
class ShowWindow(context: Context) : BaseWindow(context){
	MainViewModel.mWindowContent.observe(this) {
            Log.d(TAG, "receive mWindowContent$it")
            binding.contentTv.text = it
        }
}

这里就有一个问题,Activity是接入了LifeCycleOwner的,但是Window浮窗是没有接入的,而LiveData在订阅非全局的观察者时,需要有LifeCycleOwner参与,时候那应该怎么办呢?

LifeCycleOwner

LifeCycleOwner在LifeCycle的设计策略中扮演的是生命周期提供者的角色,一个UI组件想要接入到LifeCycle的一系列规则中,就需要实现LifeCycleOwner,比如我们熟知的ComponentActivity就是实现了LifecycleOwner,并且通过mLifecycleRegistry去做的生命周期的关联:

java 复制代码
public class ComponentActivity extends Activity implements
        LifecycleOwner,
        KeyEventDispatcher.Component {
        
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);

@SuppressLint("RestrictedApi")
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ReportFragment.injectIfNeededIn(this);
    }
}

高版本的SDK,是通过ReportFragment监听生命周期,然后再通过mLifecycleRegistry去设置。

同理,我们的Window浮窗想要去把自身的生命周期导入到LifeCycle中,也是需要实现LifecycleOwner,然后通过mLifecycleRegistry去关联生命周期,如下:

java 复制代码
abstract class BaseWindow(val context : Context) : LifecycleOwner {
	private val lifecycleRegistry = LifecycleRegistry(this)
	open fun show() {
      lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
   }

   open fun hide() {
      lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
   }
}

即在show的时候,去开启生命周期,而在hide的时候去结束生命周期。这样,内部关联的LiveData在感知到Window浮窗的生命周期onStart后,就会开启数据的更新;在感知到onDestroy的时候去移除观察者,结束数据更新,如下:

java 复制代码
// 处理STARTED事件
@Override
boolean shouldBeActive() {
    return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
 }
        
// 处理 DESTROYED事件
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
    if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
        removeObserver(mObserver); // 自动解绑
        return;
    }
    // 其他状态处理...
}

ViewBinding

ViewBinding在Window浮窗的使用没有任何障碍,通过配置gradle打开ViewBinding功能:

java 复制代码
buildFeatures {
        viewBinding = true
    }

然后就可以在对应的浮窗中使用ViewBinding:

java 复制代码
class ShowWindow(context: Context) : BaseWindow(context){
	private val binding : ShowwindowBinding by lazy { ShowwindowBinding.inflate(LayoutInflater.from(context)) }
}

这里的context是传入的ApplicationContext,同样是没问题的。

这里想说一下基类和子类怎么处理Viewbinding。比如这一类浮窗中,它们有一些共同的界面逻辑,只是其中一块显示区域的内容有差距。这时候,我们可以抽取一个BaseWindow,并在BaseWindow中实现公共的UI和逻辑,同时在界面布局中预留出子类的显示区域。这样在BaseWindow和子类Window都可以使用ViewBinding。

也可以理解为,只要有一个layout布局文件,就对应一个ViewBinding类。具体的实现如下:

Base类的布局文件预留一个子类的内容显示区域,不同的子类会填充不同的内容:

java 复制代码
<FrameLayout
            android:id="@+id/content_layout"
            android:layout_width="870dp"
            android:layout_height="match_parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintRight_toRightOf="parent"/>

Base类的代码实现:

java 复制代码
abstract class BaseWindow(context: Context){
private val binding : BaseWindowBinding by lazy { BaseWindowBinding.inflate(LayoutInflater.from(context)) }
    override fun getView(): View {
        if (binding.contentLayout.childCount == 0) {
            binding.contentLayout.addView(getContentLayout())
        }
        return binding.root
    }
    
	//预留给子类实现
    abstract fun getContentLayout() : View

在子类的实现中,使用ViewBindiing加载自己的布局模块就好,并把自己的根布局通过getContentLayout方法返回。

相关推荐
xiangxiongfly9152 小时前
Android setContentView()源码分析
android·setcontentview
人间有清欢3 小时前
Android开发补充内容
android·okhttp·rxjava·retrofit·hilt·jetpack compose
人间有清欢4 小时前
Android开发报错解决
android
每次的天空5 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin
每次的天空7 小时前
Android学习总结之Binder篇
android·学习·binder
峥嵘life7 小时前
Android 有线网开发调试总结
android
是店小二呀8 小时前
【算法-链表】链表操作技巧:常见算法
android·c++·算法·链表
zhifanxu10 小时前
Kotlin 遍历
android·开发语言·kotlin
追随远方10 小时前
Android NDK版本迭代与FFmpeg交叉编译完全指南
android·ffmpeg
柯南二号21 小时前
Android Studio根目录下创建多个可运行的模块
android·ide·android studio