背景
在制作安卓应用时发现,
可以通过绑定view上数据到ViewModel来实现view中元素赋值的简化,
因此想了解下ViewModel的具体情况,
但遇到一个羁绊–MVP模式,
一并来介绍下
MVP模式
是MVC模式的变种.
指的是model, view, presenter.
在presenter中调用model来更改model的内容,这个model不是ViewModel,
同时preseter不断调用view的接口来实现对view的操作.
原因是:
Presenters不应包含对视图的引用
这样可以
- 分离视图,数据,逻辑的代码
- view完全只负责显示,变得被动化
- 可以解决一个activity中两个fragment的通信问题
但有些缺点
- presenter负责的部分过多,既要管理界面逻辑,又要管理业务逻辑
- presenter严重依赖view的接口,业务变更后需要改动的文件多,非常繁琐
- view的接口声明
- view的接口实现
- presenter调用接口的代码
MVVM模式
是MVC的另一种变种.
指的是model, view, ViewModel
此处ViewModel代替了presenter原先的作用,
改进在于
- ViewModel和view能够双向绑定,免去接口的使用,实现了数据驱动UI
- 借用livedata可以将ViewModel的变动更新到view
- 在view中在onchangelistener中调用ViewModel的方法可以把值的变动写入到ViewModel
- livedata和ViewModel配合使用能够解决声明周期问题引起的activity在重新配置后数据丢失的问题
另外angular中常用的vm.xxx中的vm也是指ViewModel
实践
普通的一个文件中包办
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class MainActivity : AppCompatActivity() { lateinit var show: TextView private var bookName = "bookName" private var price = 1 private var author = "author"
private fun refreshView() { show.text = "$bookName: $price, $author" }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
verticalLayout { show = textView("test") button("plus1").onClick { price += 1
refreshView() } } } }
|
MVP模式
首先提取出model
1 2 3 4 5 6 7
| data class Book(var bookName:String, var price: Int, var author: String) { fun beforeRefresh(): String { return "$bookName: $price, $author" } }
|
然后是被动显示的view
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| interface IView { fun refreshView(str: String) }
class MainActivity : AppCompatActivity(), IView { lateinit var show: TextView private lateinit var book: Book lateinit var ipresenter: IPresenter
override fun refreshView(str: String) { show.text = str }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
book = Book("bookName", 1, "author") ipresenter = IPresenter(book, this)
verticalLayout { show = textView("test") button("plus1").onClick { ipresenter.plus1() } } } }
|
最后是执行页面逻辑和业务逻辑的presenter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface Ipresent { fun plus1() }
class IPresenter(private var book: Book, private var activity: IView) : Ipresent{
override fun plus1() { book.price += 1
activity.refreshView(book.beforeRefresh()) }
}
|
MVVM模式
model可以完全不变
使用ViewModel需要在gradle里声明使用的包,目前最新的是
这里包含了livedata和ViewModel
1
| implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
view摆脱了接口,迎来了observer的绑定,同时可以承担一部分的页面逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class MainActivity : AppCompatActivity() { private lateinit var show: TextView private val mMainActivityViewModel by lazy { ViewModelProvider(this).get(MainActivityViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
verticalLayout { show = textView("test") button("plus1").onClick { mMainActivityViewModel.plus1() } }
mMainActivityViewModel.displayStr.observe(this, Observer<String> { show.text = it }) } }
|
ViewModel里面保留了原先的业务操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MainActivityViewModel : ViewModel() { private val mDisplayStr = MutableLiveData<String> () val displayStr: LiveData<String> get() = mDisplayStr
private var book = Book("bookName", 1, "author")
fun plus1(){ book.price += 1 mDisplayStr.postValue(book.beforeRefresh()) } }
|
附
livedata如何解决activity重新配置问题
当配置发生更改,activity的行为是 onPause()
, onStop()
, onDestroy()
,
onCreate()
, onStart()
, onResume()
(参考官方说明)
ViewModel是使用ViewModelProvider创建的,
而实现上,ViewModel被保存在缓存中的ViewModelStore中
从onDestroy的实现来看,如果是配置更改引起的onDestroy,ViewModelStore不发出clear信号.
如何将view变动通知ViewModel
可以在onchangelistener中调用viewmodel的setter,
如果需要实时实现效果,可以定义扩展函数来增强功能,
减少调用时候的代码.
其次也可以不是实时的,
但这两种方法都不能令人满意.
谷歌有推出databinding,但需要借用DataBindingUtil.inflate(xx, layout~id~)
同时在xml中使用特殊写法 @={}
写上要bind的对象.
如何绑定大量的数据到view?
如果使用databinding,则可以不需要显式指明要observe的对象,算是一个解决办法.
参考
- mvp与mvvm的区别
- livedata的用法
- ViewModel应该提供哪些信息