MVVM
MVVM: 数据绑定与无状态的视图
MV*
家族中最年轻的一位,也是由
- 用户交互输入
View 将数据直接传送给ViewModel ,ViewModel 保存这些状态数据- 在有需要的情况下,
ViewModel 会将数据传送给Model Model 在更新完成之后通知ViewModel ViewModel 从Model 中获取最新的模型,并且更新自己的数据状态View 根据最新的ViewModel 的数据进行重新渲染
根据上述流程,我们可知
- ViewModel、
Model 中存在ViewLogic 实现,View 则不保存任何状态信息 View 不需要了解ViewModel 的实现细节,但是会声明自己所需要的数据类型,并且能够知道如何重新渲染ViewModel 不需要了解View 的实现细节( 非命令式编程) ,但是需要根据View 声明的数据类型传入对应的数据。ViewModel 需要了解Model 的实现细节。Model 不需要了解View 的实现细节,需要了解ViewModel 的实现细节
iOS
import UIKit
struct Person { // Model
let firstName: String
let lastName: String
}
protocol GreetingViewModelProtocol: class {
var greeting: String? { get }
var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
init(person: Person)
func showGreeting()
}
class GreetingViewModel : GreetingViewModelProtocol {
let person: Person
var greeting: String? {
didSet {
self.greetingDidChange?(self)
}
}
var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
required init(person: Person) {
self.person = person
}
func showGreeting() {
self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
}
}
class GreetingViewController : UIViewController {
var viewModel: GreetingViewModelProtocol! {
didSet {
self.viewModel.greetingDidChange = { [unowned self] viewModel in
self.greetingLabel.text = viewModel.greeting
}
}
}
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
}
// layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel
Distribution: 在Cocoa MVVM 中,View 相对于MVP 中的View 担负了更多的功能,譬如需要构建数据绑定等等Testability:ViewModel 拥有View 中的所有数据结构,因此很容易就可以进行测试- 易用性
: 相对而言有很多的冗余代码
Android
XML 中声明数据绑定
<data>
<variable
name="viewModel"
type="uk.ivanc.archimvvm.viewmodel.MainViewModel"/>
</data>
...
<EditText
android:id="@+id/edit_text_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/button_search"
android:hint="@string/hit_username"
android:imeOptions="actionSearch"
android:inputType="text"
android:onEditorAction="@{viewModel.onSearchAction}"
android:textColor="@color/white"
android:theme="@style/LightEditText"
app:addTextChangedListener="@{viewModel.usernameEditTextWatcher}"/>
View 中绑定ViewModel
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
mainViewModel = new MainViewModel(this, this);
binding.setViewModel(mainViewModel);
setSupportActionBar(binding.toolbar);
setupRecyclerView(binding.reposRecyclerView);
ViewModel 中进行数据操作
public boolean onSearchAction(TextView view, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String username = view.getText().toString();
if (username.length() > 0) loadGithubRepos(username);
return true;
}
return false;
}
public void onClickSearch(View view) {
loadGithubRepos(editTextUsernameValue);
}
public TextWatcher getUsernameEditTextWatcher() {
return new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
editTextUsernameValue = charSequence.toString();
searchButtonVisibility.set(charSequence.length() > 0 ? View.VISIBLE : View.GONE);
}
@Override
public void afterTextChanged(Editable editable) {
}
};
}