自定义View中获取ViewModel

1,305 阅读3分钟

RE4wwuV.jpeg

Jetpack系列:
Lifecycle使用方法、原理分析 - 掘金 (juejin.cn)
LiveData使用方法、原理分析 - 掘金 (juejin.cn)
ViewModel使用方法、原理分析(一) - 掘金 (juejin.cn)
ViewModel使用方法、原理分析(二) - 掘金 (juejin.cn)
自定义View中获取ViewModel - 掘金 (juejin.cn)

假设有种业务场景,一个自定义View中包含很多数据、状态并且需要将这些数据状态进行保存,在旋转屏幕时数据不丢失,Jetpack组件中可以通过ViewModel进行实现,是否可以在自定义View管理数据呢?

在ViewModel源码分析中,回顾Activity、Fragment创建ViewModel过程如下:

//获取Activity、Fragment宿主中保存的ViewModel
val viewModel1 = ViewModelProvider(this)[CustomViewModel::class.java]
//在Fragment中获取宿主Activity中保存的ViewModel
val viewModel2 = activity?.let { ViewModelProvider(it)[CustomViewModel::class.java] }

在ViewModelProvider源码中发现,创建ViewModelProvider的常用2种方式:

public constructor(owner: ViewModelStoreOwner)
public constructor(owner: ViewModelStoreOwner, factory: Factory)

按照以上方式,只需要在自定义View中获取到ViewModelStoreOwner就可以创建ViewModel,在源码阅读的过程中发现有个ViewTreeViewModelStoreOwner,之前源码阅读时一直不理解这个类意义,那就分析源码:

image.png

在ViewTreeViewModelStoreOwner只有set()、get()方法,原理很简单,通过View的setTag()保存一个ViewModelStoreOwner对象,get()方法就是遍历View树中所有key为R.id.view_tree_view_model_store_owner的对象直到获取到对应的保存一个ViewModelStoreOwner对象

public class ViewTreeViewModelStoreOwner {
    
    public static void set(@NonNull View view, @Nullable ViewModelStoreOwner viewModelStoreOwner) {
        view.setTag(R.id.view_tree_view_model_store_owner, viewModelStoreOwner);
    }

    @Nullable
    public static ViewModelStoreOwner get(@NonNull View view) {
        ViewModelStoreOwner found = (ViewModelStoreOwner) view.getTag(
                R.id.view_tree_view_model_store_owner);
        if (found != null) return found;
        ViewParent parent = view.getParent();
        while (found == null && parent instanceof View) {
            final View parentView = (View) parent;
            found = (ViewModelStoreOwner) parentView.getTag(R.id.view_tree_view_model_store_owner);
            parent = parentView.getParent();
        }
        return found;
    }
}

再继续分析,源码中调用set()的时机,发现在ComponentActivity中有个initViewTreeOwners(),这不就是View所在Activity对象,也就是跟Activity、Fragment创建时所使用的ViewModelStoreOwner

private void initViewTreeOwners() {
    ViewTreeLifecycleOwner.set(getWindow().getDecorView(), this);
    ViewTreeViewModelStoreOwner.set(getWindow().getDecorView(), this);
    ViewTreeSavedStateRegistryOwner.set(getWindow().getDecorView(), this);
    ViewTreeOnBackPressedDispatcherOwner.set(getWindow().getDecorView(), this);
}

这几个Tag是不是都很熟悉,在前几篇源码分析中都有介绍过,另外ViewTreeOnBackPressedDispatcherOwner是最新库中新加入的,主要为了适配Activity、Fragment处理返回事件混乱问题

那么在自定义View中就可以通过ViewTreeViewModelStoreOwner获取该自定义View所在宿主Activity,继续扒源码ViewTreeViewModelStoreKt.kt

public fun View.findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? =
    ViewTreeViewModelStoreOwner.get(this)

这样就可以试试在自定义View中如何创建ViewModel对象,在ViewTreeViewModelStoreOwner中get()方法中View时不停地向上遍历查询对应的ViewModelStoreOwner,所以需要在onAttachedToWindow()方法中执行创建方法

class WithViewModelCustomView: FrameLayout {
    constructor(context:Context) : super(context) {
        initView(context)
    }
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initView(context)
    }

    private fun initView(context: Context){
        LayoutInflater.from(context).inflate(R.layout.custom_fragment, this, true)

    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        val viewModel = findViewTreeViewModelStoreOwner()?.let {
            ViewModelProvider(it)[CustomViewModel::class.java]
        }
        findViewTreeLifecycleOwner()?.let { lifecycleOwner ->
            viewModel?.data1?.observe(lifecycleOwner){
                findViewById<TextView>(R.id.tv1).text = it
            }
        }
    }
}

通过以上方式就可以在自定义View中创建对应的ViewModel,保存Jetpack中的所有优势

再模仿Activity、Fragment ktx扩展函数中创建viewmodel方式,给View来个扩展函数:

@MainThread
public inline fun <reified VM: ViewModel> View.viewOfViewModel(): Lazy<VM> {
    val viewModelStoreOwner = findViewTreeViewModelStoreOwner()!!
    return ViewModelLazy(VM::class, {viewModelStoreOwner.viewModelStore}, {(viewModelStoreOwner as HasDefaultViewModelProviderFactory).defaultViewModelProviderFactory}, { CreationExtras.Empty})
}
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
//        val viewModel = findViewTreeViewModelStoreOwner()?.let {
//            ViewModelProvider(it)[CustomViewModel::class.java]
//        }
//        findViewTreeLifecycleOwner()?.let { lifecycleOwner ->
//            viewModel?.data1?.observe(lifecycleOwner){
//                findViewById<TextView>(R.id.tv1).text = it
//            }
//        }

        val viewModel1 by viewOfViewModel<CustomViewModel>()
        findViewTreeLifecycleOwner()?.let { lifecycleOwner ->
            viewModel1.data1.observe(lifecycleOwner){
                findViewById<TextView>(R.id.tv1).text = it
            }
        }
    }