[关闭]
@Awille 2019-01-26T13:02:13.000000Z 字数 6622 阅读 106

MVVM 与 data binding

设计模式 Android mvvm


1、序言

1.1、目前开发过程中的MVP

在mvp设计模式当中,m是model,是数据层,在android开发中,主要是负责一些数据的更新工作,数据更新的方式或是网络获取或是数据库获取。v是view,主要是负责UI的显示与更新。P层是presenter,P层一般会持有view的实例,P层接受view层传入的信息去通知model层进行数据的更新,model层数据更新后,P层用持有的view实例调用相关方法进行view层的更新。在android开发当中,activity或者fragment一般会实现view层的方法,除了这些方法还会做一些UI的初始化与为各个控件添加监听器的工作。

1.2、MVP在android开发中带来的问题

上面提到activity跟fragment主要做的事情可以分为三部分:控件初始化、为控件设置监听器、实现mvp中view层的相关方法。这三部分中,实现view层的相关方法代码量一般来说在activity或者frament中占比是最大的,也正是这一部分代码,导致了activity或者fragment变得很臃肿,可能出现一个activity中有上千行代码的情况。

1.3 MVVM 与 MVP

MVVM当中,以 VM(View Model) 代替了 MVP 的 P(Presenter)。MVP当中,M与V层被隔离,M与V直接通过P层进行交流。
View层接受用户数据后将信息传递给P层,P层直接读取或者更新再读取model中的数据,读取到的数据再对View层进行UI更改。这里出现的问题除了上面提到的造成Activity或者fragment臃肿以外,P层对model层的数据操作与对View层的更新都是基于接口的,所以当业务场景越来越多的时候,接口也会越来越多,这也是MVP模式开发的问题之一。而MVVM同样也可以解决这个问题。

2、MVVM(Model - View - ViewModel)

在MVVM中,View层和ViewModel双向绑定。在android开发之中,这种view与viewmodel双向绑定的模式可以由dataBinding来实现。在mvvm中,viewmodel作用是创建关联,将model和view绑定起来,如此之后,我们model的更改,通过viewmodel反馈给view,从而自动刷新界面。双向绑定可以理解为View层可以向viewmodel发送数据,而viewmodel发生改变时可以直接更新view状态。viewmodel完全是用来做业务逻辑的,在mvvm中,model层的数据其实是已经具体绑定在view中的某个具体控件中的,所以viewmodel只要根据业务逻辑做好对model中data的更新就可以了。

3、DataBinding Demo

DataBinding不用专门引入依赖,只需要在使用的模块中打开dataBinding的开关即可

  1. android {
  2. ...
  3. dataBinding {
  4. enabled = true;
  5. }
  6. }

model层数据实体类编写:

  1. public class User implements Serializable {
  2. private String firstName;
  3. private String lastName;
  4. public User(String firstName, String lastName) {
  5. this.firstName = firstName;
  6. this.lastName = lastName;
  7. }
  8. public String getFirstName() {
  9. return firstName;
  10. }
  11. public void setFirstName(String firstName) {
  12. this.firstName = firstName;
  13. }
  14. public String getLastName() {
  15. return lastName;
  16. }
  17. public void setLastName(String lastName) {
  18. this.lastName = lastName;
  19. }
  20. }

编写view层的布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity">
  8. <data>
  9. <import type="com.example.databindingtest.model.User"/>
  10. <variable name="user" type="User" />
  11. </data>
  12. <LinearLayout
  13. android:orientation="vertical"
  14. android:layout_width="match_parent"
  15. android:layout_height="match_parent">
  16. <TextView
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text="@{user.firstName}"
  20. android:textSize="20sp"/>
  21. <Button
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:text="@{user.lastName}"
  25. android:textSize="25sp"/>
  26. </LinearLayout>
  27. </layout>

在编写上面的标签的内容的的时候,编译器可能会报这个错误

Attribute is missing the Android namespace prefix

这里要注意到最外层一定要是用layout包住的,其他linearLayout, relativeLayout都不行。另外dataBinding的使用对环境是有要求的,Android Studio版本在1.3以上 gradle的版本要在1.5.0-alpha1以上。目前IDE应该都是3.0版本以上的,报错一般是gradle版本过低造成的,升级一下gradle版本即可。gradle升级指南
不过应该不会出现这个问题,现在一般gradle版本都在4.0版本以上了.

mainAcitivity中的代码:

  1. public class MainActivity extends AppCompatActivity {
  2. private ActivityMainBinding binding;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
  7. User user = new User("Will", "Chung");
  8. binding.setUser(user);
  9. }
  10. }

以上是一个databinding的简单实用。以上的例子中我们看到了view层具体的控件直接与model层相应的数据直接绑定在一起。

4、DataBinding使用入门

在上面的简单demo中我们看到一种view与数据进行直接绑定的方式。但是上面的例子只是一个单向的绑定,binding.setUser(user)这行代码过后,当user对象发声改变时,view层不会相应做具体的更新。

正向绑定:当model层数据对象改变时,view层做出相应更新
反向绑定:当view层变化时,其绑定的数据对象做出相应的更新。

4.1、正向绑定实现方式

4.1.1、继承Observable

a、实体类继承BaseObservale类
b、Getter上使用注解@Bindable
c、在Setter里调用方法notifyPropertyChanged

  1. package com.example.databindingtest.model;
  2. import android.databinding.BaseObservable;
  3. import android.databinding.Bindable;
  4. import com.android.databinding.library.baseAdapters.BR;
  5. import java.io.Serializable;
  6. public class User extends BaseObservable {
  7. private String name;
  8. public User(String name) {
  9. this.name = name;
  10. }
  11. @Bindable
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. notifyPropertyChanged(BR.name);
  18. }
  19. }

当get方法添加注解后,BR这个类中会自动添加name这个属性

  1. package com.android.databinding.library.baseAdapters;
  2. public class BR {
  3. public static final int _all = 0;
  4. public static final int name = 1;
  5. public static final int user = 2;
  6. }

4.1.2、定义ObservableField方式

Data Binding还提供了一系列Observable,这样就不用每个绑定都创建obeservable类了

  1. private static class User {
  2. public final ObservableField<String> firstName =new ObservableField<>();
  3. public final ObservableField<String> lastName =new ObservableField<>();
  4. public final ObservableInt age = new ObservableInt();
  5. }

java中访问方式为:

  1. user.firstName.set("Google");
  2. int age = user.age.get();

4.2、反向绑定

4.2.1、少部分控件支持在XML中的引用中把“@{}”改成“@={}”实现反向绑定

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <layout xmlns:android="http://schemas.android.com/apk/res/android">
  3. <data>
  4. <import type="com.example.databindingtest.model.User"/>
  5. <variable name="user" type="User" />
  6. </data>
  7. <LinearLayout
  8. android:layout_height="match_parent"
  9. android:layout_width="match_parent">
  10. <EditText
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:text="@={user.name}"
  14. android:textSize="20sp"/>
  15. </LinearLayout>
  16. </layout>

4.2.2 自定义控件实现反向绑定

随便弄一个自定义控件,比如继承view一个可以设置max属性的控件

  1. public class RangeBar extends View {
  2. private int max;
  3. public RangeBar(Context context) {
  4. super(context);
  5. }
  6. public void setMax(int max)
  7. {
  8. this.max = max;
  9. invalidate();
  10. }
  11. }

创建该属性值绑定的model层的实体类:

  1. public class Range extends BaseObservable {
  2. int max;
  3. @Bindable
  4. public int getMax()
  5. {
  6. return max;
  7. }
  8. public void setMax(int max)
  9. {
  10. this.max = max;
  11. notifyPropertyChanged(BR.max);
  12. }
  13. }

添加该控件

  1. <layout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:app="http://schemas.android.com/apk/res-auto">
  3. <data>
  4. <import type="com.example.databindingtest.model.RangeBar"/>
  5. <variable name="range" type="RangeBar"/>
  6. </data>
  7. <LinearLayout
  8. android:layout_height="match_parent"
  9. android:layout_width="match_parent">
  10. <com.example.databindingtest.model.RangeBar
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. app:max ="@={range.max}"/>
  14. </LinearLayout>
  15. </layout>

在自定义view的上方添加反向绑定

  1. @InverseBindingMethods({
  2. @InverseBindingMethod(
  3. type = com.example.databindingtest.model.Range.class,
  4. attribute = "max",
  5. event = "maxAttrChanged",
  6. method = "getMax")})
  7. public class RangeBar extends View {
  8. ...
  9. }

设置监听器:该监听器主要是用来改变其对应类实例数据的值

  1. private static InverseBindingListener maxInverseBindingListener;
  2. @BindingAdapter("maxAttrChanged")
  3. public static void setMaxChangedListener(RangeBar rangeBar,final InverseBindingListener bindingListener) {
  4. maxInverseBindingListener = bindingListener;
  5. }

查看该监听器:

  1. public interface InverseBindingListener {
  2. /**
  3. * Notifies the data binding system that the attribute value has changed.
  4. */
  5. void onChange();
  6. }

这里的onChange方法就是用来更新对用数据对象的值的,我们在需要的时候调用就可以了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注