有一个需求:需要查询用户账号信息,用户输入账号,点击按钮可进行查询账号信息,如果查询数据成功,则将数据展示在界面上;如果查询数据失败,则在界面上提升获取数据失败。
使用一个Activity完成所有功能
NormalActivity
实现的效果如下,输入用户名,点击提交,查询用户信息,获取信息成功展示成功页,获取信息失败展示失败页。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/userName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:hint="请输入用户名"
android:textSize="14sp" />
<Button
android:id="@+id/submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:text="提交" />
<TextView
android:id="@+id/result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textSize="14sp" />
LinearLayout>
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.juny.mmm.bean.Account
import com.juny.mmm.callback.MCallback
import kotlinx.android.synthetic.main.activity_normal.*
import java.util.*
class NormalActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_normal)
initEvent()
}
private fun initEvent() {
submit.setOnClickListener {
getAccountData(getUserInput(), object : MCallback {
override fun onSuccess(account: Account) {
showSuccessPage(account)
}
override fun onFailed() {
showFailedPage()
}
})
}
}
// 获取用户输入信息
private fun getUserInput(): String {
return userName.text.toString()
}
// 展示获取数据成功的界面
private fun showSuccessPage(account: Account) {
result.text = "用户账号:" + account.name + " | " + "用户等级:" + account.level
}
// 展示获取数据失败的界面
private fun showFailedPage() {
result.text = "获取数据失败"
}
// 模拟查询账号数据
private fun getAccountData(accountName: String, callback: MCallback) {
val random = Random()
val isSuccess = random.nextBoolean()
if (isSuccess) {
val account = Account(accountName, 100)
callback.onSuccess(account)
} else {
callback.onFailed()
}
}
}
用户信息实体类:使用了 Kotlin 中的数据类来进行实现
data class Account(var name: String, var level: Int)
回调接口:对于数据请求的成功和失败提供相关接口
interface MCallback {
fun onSuccess(account: Account)
fun onFailed()
}
一般模式中,所有的功能都是堆积在一个Activity中的,导致Activity中代码的可复用性降低,Activity过于累赘。
MVC的全名是Model View Controller,即模型(model)- 视图(view)- 控制器(controller)。
流程:
让 Controller 持有 Model的引用;而 Model 要向 View 传递数据一般不会让 Model 持有 View 的引用,而是类似 CallBack 的注册监听的方式进行数据的传递。
MVC 主要是把数据处理这块的逻辑给放到 Model 层中进行处理。
class MVCModel {
// 模拟查询账号数据
fun getAccountData(accountName: String, callback: MCallback) {
val random = Random()
val isSuccess = random.nextBoolean()
if (isSuccess) {
val account = Account(accountName, 100)
callback.onSuccess(account)
} else {
callback.onFailed()
}
}
}
class MVCActivity : AppCompatActivity() {
//Activity 中持有 Model 的引用
private lateinit var mMVCModel: MVCModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_normal)
mMVCModel = MVCModel()
initEvent()
}
private fun initEvent() {
submit.setOnClickListener {
mMVCModel.getAccountData(getUserInput(), object : MCallback {
override fun onSuccess(account: Account) {
showSuccessPage(account)
}
override fun onFailed() {
showFailedPage()
}
})
}
}
// 获取用户输入信息
private fun getUserInput(): String {
return userName.text.toString()
}
// 展示获取数据成功的界面
private fun showSuccessPage(account: Account) {
result.text = "用户账号:" + account.name + " | " + "用户等级:" + account.level
}
// 展示获取数据失败的界面
private fun showFailedPage() {
result.text = "获取数据失败"
}
}
这样我们就将数据请求的部分代码抽离了出去,通过引用进行调用,让代码更加灵活、干净。
优点:
一定程度上的实现了 Model 与 View 的分离,降低了代码的耦合性。
缺点:
Controller 与 View 难以完全解耦,并且随着项目复杂度的提升,Controller 将会越来越臃肿,Activity 承担了控制器的功能,又要承担部分视图层的工作。
其实 MVC 模式的实现也可以这样进行表示:
这样就体现出来了 View 和 Controller 的耦合关系。
MVP 的全称为 Model - View - Presenter 模型,他是将 Model 和 View 隔离开来,两者之间不相互作用,而是通过 Presenter 作为一个中间件进行通信;Presenter负责逻辑的处理,Model提供数据,View负责显示。
interface IMVPView {
fun getUserInput(): String
fun showSuccessPage(account: Account)
fun showFailedPage()
}
class MVPActivity : AppCompatActivity(), IMVPView {
private lateinit var mMVPPresenter: MVPPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_normal)
mMVPPresenter = MVPPresenter(this)
initEvent()
}
private fun initEvent() {
submit.setOnClickListener {
mMVPPresenter.getData()
}
}
// 获取用户输入信息
override fun getUserInput(): String {
return userName.text.toString()
}
// 展示获取数据成功的界面
override fun showSuccessPage(account: Account) {
result.text = "用户账号:" + account.name + " | " + "用户等级:" + account.level
}
// 展示获取数据失败的界面
override fun showFailedPage() {
result.text = "获取数据失败"
}
}
与MVC 中的 Model 的作用相同
class MVPModel {
// 模拟查询账号数据
fun getAccountData(accountName: String, callback: MCallback) {
val random = Random()
val isSuccess = random.nextBoolean()
if (isSuccess) {
val account = Account(accountName, 100)
callback.onSuccess(account)
} else {
callback.onFailed()
}
}
}
Presenter 持有 View 和 Model 的引用,通过 Presenter 我们向 Model 请求相关数据,并根据判断将结果返回 View 上面显示出来。
class MVPPresenter {
private val mView: IMVPView
private val mModel: MVPModel
constructor(mView: IMVPView) {
this.mView = mView
mModel = MVPModel()
}
fun getData() {
mModel.getAccountData(mView.getUserInput(), object : MCallback {
override fun onSuccess(account: Account) {
mView.showSuccessPage(account)
}
override fun onFailed() {
mView.showFailedPage()
}
})
}
}
优点:
解决了 MVC 中 Controller 与 View 过度耦合的缺点,职责划分明显,更加易于维护。
缺点:
接口数量过多,项目复杂的升高。随着项目的复杂度升高, Presenter 层将会越来越臃肿
结合上面所说的优缺点,有几条关于 MVP 的使用建议:
MVVM 的全称是 Model - View - ViewModel 模型,他的模型结构和 MVP 很相像,但是在代码逻辑上 MVVM 会显得更加简洁,其实他就是将 Presenter 换为了 ViewModel。
MVVM 在 MVP 的基础上实现了数据视图的绑定(DataBinding),这样的话就不用使用接口进行传递了,而是当数据变化的时候,视图会自动更新;反之,当视图发生改变的时候,数据也会进行自动更新。
好处:
首先我们需要在app的 build.gradle 中声明使用 DataBinding
在 android 中添加下面代码
android {
dataBinding {
enabled = true
}
}
使用 Kotlin 的话还需要添加下面代码
apply plugin: 'kotlin-kapt'//需要使用kapt作为注解处理器
kapt {
generateStubs = true
}
Model 和 MVP 中的一样
class MVVMModel {
// 模拟查询账号数据
fun getAccountData(accountName: String, callback: MCallback) {
val random = Random()
val isSuccess = random.nextBoolean()
if (isSuccess) {
val account = Account(accountName, 100)
callback.onSuccess(account)
} else {
callback.onFailed()
}
}
}
分析ViewModel中需要哪些变量以及方法,比如:getData(),需要调用 Model 中的 getAccountData 方法去获取数据,因为我们是使用 DataBinding 方法,所以在 ViewModel 中还需要记录对应的返回值用于展现,因为希望和数据变更进行绑定,防止每次都要更新所有的数据,所以类需要继承 BaseObservable。然后在元素的 get 和 set 方法做出修改,get 方法需要添加注解 @Bindable 而 set 方法中需要加入更新该元素显示的代码 notifyPropertyChanged(BR.XXX); ,BR 是什么呢,其实就是相当于 R 文件,用来确定是那个控件需要更新,只不过这里可以直接用变量名。
class MVVMViewModel : BaseObservable() {
private val mvvmModel: MVVMModel = MVVMModel()
@get:Bindable
var userInput: String? = null
set(userInput) {
field = userInput
notifyPropertyChanged(BR.userInput)
}
@get:Bindable
var result: String? = null
set(result) {
field = result
notifyPropertyChanged(BR.result)
}
fun getData(view: View) {
this.userInput?.let {
mvvmModel.getAccountData(it, object : MCallback {
override fun onSuccess(account: Account) {
result = "用户账号:" + account.name + " | " + "用户等级:" + account.level
}
override fun onFailed() {
result = "获取数据失败"
}
})
}
}
}
将 ViewModel 作为布局文件的参数,在布局文件中进行调用。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.juny.mmm.mvvm.MVVMViewModel" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/userName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:hint="请输入用户名"
android:text="@={viewModel.userInput}"
android:textSize="14sp" />
<Button
android:id="@+id/submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
android:onClick="@{viewModel.getData}"
android:text="提交" />
<TextView
android:id="@+id/result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="@{viewModel.result}"
android:textSize="14sp" />
LinearLayout>
layout>
绑定数据,修改原有的 setContentView 方法,改为使用 DataBindingUtil.setContentView 方法进行绑定,然后我们将我们需要的 ViewModel 传入进去。
DataBinding 有单向绑定和双向绑定的区别,如果是单向绑定的话就是直接用 @{XXX} 的形式,如果是需要双向绑定的话就是需要改为 @={XXX} 的形式。
双向绑定的好处就是不仅在数据变化的时候进行刷新 View,也会在 View 主动修改数据的时候对数据进行更新,多用于编辑框等控件。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.juny.mmm.R
import com.juny.mmm.databinding.ActivityMvvmBinding
class MVVMActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var binding = DataBindingUtil.setContentView<ActivityMvvmBinding>(this, R.layout.activity_mvvm)
var mvvmViewModel = MVVMViewModel()
binding.viewModel = mvvmViewModel
}
}
优点:
实现了数据和视图的双向绑定,极大的简化代码
缺点:
Bug 难以调试,学习成本较大。
框架名 | 总结 |
---|---|
MVC | 学习简单,但是解耦不够彻底 |
MVP | 解耦更加彻底,学习起来较为简单,但是代码相对较为繁琐 |
MVVM | 代码逻辑简洁,但是学习成本较大 |