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

相关推荐
恋猫de小郭1 小时前
再次紧急修复,Flutter 针对 WebView 无法点击问题增加新的快速修复
android·前端·flutter
李慕婉学姐1 小时前
【开题答辩过程】以《基于Android的健康助手APP的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
android·java·mysql
似霰3 小时前
传统 Hal 开发笔记6----App 访问硬件服务
android·framework·hal
爱装代码的小瓶子4 小时前
【c++知识铺子】封装map和set(详细版)
android·java·c++
私人珍藏库4 小时前
AutoGLM无需豆包手机,让AI自动帮你点外卖-刷视频
android·ai·智能手机·工具·软件·辅助·autoglm
孤舟簔笠翁5 小时前
【Android驱动14】Android系统Crash工具使用方法和分析
android
帅得不敢出门6 小时前
MTK Android11 APP调用OTA升级
android·java·开发语言·framework
2501_915909066 小时前
苹果应用加密方案的一种方法,在没有源码的前提下,如何处理 IPA 的安全问题
android·安全·ios·小程序·uni-app·iphone·webview
用户2018792831676 小时前
Android App 换肤原理:用 "装修小房子" 故事浅谈
android