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方法返回。

相关推荐
stevenzqzq8 小时前
android Hilt注解
android
望佑11 小时前
自定义Scrollbar的两种实现方式
android
望佑11 小时前
记录一次完整ANR日志及分析
android
Android小码家12 小时前
Live555+Windows+MSys2 编译Androidso库和运行使用
android·live555
水w12 小时前
【Android】基础架构(详细介绍)
android·android studio
望佑12 小时前
低成本实现媒体文件预览
android
_祝你今天愉快13 小时前
安卓源码学习之【开机向导定制 OOBE/Provision源码分析】
android·源码
技术野侠客13 小时前
Android端部署DeepSeek
android·人工智能
懋学的前端攻城狮13 小时前
Android 一些基础-05-导航与 Tab
android
Tee xm13 小时前
清晰易懂的 Kotlin 安装与配置教程
android·开发语言·kotlin