重构自定义View时,如何使用Android架构组件的ViewModel?
你遇到的问题其实很常见——ViewModelProviders.of()(现在已经被废弃,推荐直接用ViewModelProvider构造方法)确实需要依赖ViewModelStoreOwner(Activity和Fragment都实现了这个接口)来管理ViewModel的生命周期。不用急着把自定义View换成Fragment,这里有几个更合适的解决方案:
方案一:通过外部传入ViewModelStoreOwner
这是最灵活且符合架构设计的方式,让自定义View保持UI组件的职责,ViewModel的生命周期由宿主(Activity/Fragment)管理。
步骤1:修改自定义View的代码
在View中添加初始化ViewModel的方法,接收ViewModelStoreOwner参数:
class MyCustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private lateinit var myViewModel: MyViewModel // 让宿主调用这个方法传入ViewModelStoreOwner fun attachViewModel(owner: ViewModelStoreOwner) { myViewModel = ViewModelProvider(owner)[MyViewModel::class.java] // 观察ViewModel的数据变化,需要同时传入LifecycleOwner(Activity/Fragment也实现了这个接口) (owner as LifecycleOwner).let { lifecycleOwner -> myViewModel.userData.observe(lifecycleOwner) { data -> // 根据数据更新View的UI,比如setText、setImage等 updateViewContent(data) } } } private fun updateViewContent(data: String) { // 具体的UI更新逻辑 } }
步骤2:在宿主Activity/Fragment中初始化
在Activity或Fragment里找到这个自定义View,调用attachViewModel并传入this:
// 在Activity中 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val customView = findViewById<MyCustomView>(R.id.my_custom_view) customView.attachViewModel(this) }
这个方案的好处是:
- 自定义View只负责UI渲染和交互,不处理生命周期逻辑
- ViewModel的生命周期和宿主绑定,配置变化时数据不会丢失
- 灵活性高,同一个View可以在不同宿主中复用,绑定不同的ViewModel实例
方案二:从Context中自动获取ViewModelStoreOwner
如果你不想让宿主手动调用初始化方法,可以尝试从View的Context中获取ViewModelStoreOwner,但要注意只有当Context是Activity或Fragment的Context时才有效:
class MyCustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private val myViewModel: MyViewModel init { // 尝试将Context强转为ViewModelStoreOwner val owner = context as? ViewModelStoreOwner ?: throw IllegalArgumentException("MyCustomView必须在Activity或Fragment的布局中使用") myViewModel = ViewModelProvider(owner)[MyViewModel::class.java] // 观察数据 (owner as? LifecycleOwner)?.let { myViewModel.userData.observe(it) { data -> updateViewContent(data) } } } private fun updateViewContent(data: String) { // UI更新逻辑 } }
这个方案的缺点是限制了View的使用场景,如果Context是Application或者其他非宿主Context,会直接抛出异常,适合你能确保View只在Activity/Fragment布局中使用的场景。
是否应该用Fragment替代自定义View?
不推荐,除非你的自定义View已经包含了大量业务逻辑、导航逻辑,更像是一个独立的页面模块。Fragment的职责是页面的一部分,有自己的生命周期和布局管理,而自定义View的核心职责是UI组件的渲染和交互。为了使用ViewModel就替换成Fragment,会增加不必要的复杂度,违反单一职责原则,反而让代码更难维护。
综上,通过传入ViewModelStoreOwner的方式,既能让自定义View用上ViewModel,又能保持组件的职责清晰,是最优解。
内容的提问来源于stack exchange,提问作者jakub

