MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开
MVVM的优点
可重用性:你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。 在Android中,布局里可以进行一个视图逻辑,并且Model发生变化,View也随着发生变化
低耦合:以前Activity、Fragment中需要把数据填充到View,还要进行一些视图逻辑。现在这些都可在布局中完成,甚至都不需要再Activity、Fragment去findViewById。这时候Activity、Fragment只需要做好的逻辑处理就可以了
MVVM
角色
View: 对应于Activity和XML,负责View的绘制以及与用户交互
Model: 实体模型
ViewModel: 负责完成View与Model间的交互,负责业务逻辑
数据驱动
在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。获取用户的输入和操作也需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多
MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具
DataBinding支持的表达式
- 数学表达式:
+ - / * % - 字符串拼接:
+ - 逻辑表达式:
&& || - 位操作符:
& | ^ - 一元操作符:
+ - ! ~ - 位移操作符:
>> >>> << - 比较操作符:
== > < >= <= instanceof- 分组操作符:
() - 字面量:
character, String, numeric, null - 强转、方法调用
 - 数组访问:
[] - 三元操作符:
?:android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
支持dimen,还支持color、string、drawable、anim等 - 聚合判断(
Null Coalescing Operator)语法??android:text="@{user.userName ?? user.realName}"
意思是如果userName为null,则显示realName - 字符拼接
1
2
3
4<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`username is :`+user.username}"/> 
这里的字符拼接不是用单引号哦,用的是ESC按键下面那个按键按出来的。目前DataBinding中的字符拼接还不支持中文
官方手册:https://developer.android.com/tools/data-binding/guide.html
MVVM用法
模板写法
1  | <layout xmlns:android="http://schemas.android.com/apk/res/android" >  | 
自定义Binding类名(Custom Binding Class Names)
- 以为根节点布局,
android studio默认会自动产生一个Binding类。类名为根据布局名产生,如一个名为activity_simple的布局,它的Binding类为ActivitySimpleBinding,所在包为app_package/databinding - 也可以自定义Binding类的名称和包名
1
2
3<data class="CustomBinding"></data> 在app_package/databinding下生成CustomBinding
<data class=".CustomBinding"></data> 在app_package下生成CustomBinding
<data class="com.example.CustomBinding"></data> 明确指定包名和类名 
Layout布局中Includes 标签使用
1  | <include layout="@layout/name"  | 
BaseObservable的方式
使User继承BaseObservable,在get方法上加上注解@Bindable,会在BR(BR类自动生成的)生成该字段标识(int)
set方法里notifyPropertyChanged(BR.field);1
2
3
4
public String getUserName() {
    return userName;
}
理解实例
在项目gradle的android标签下中添加1
2
3dataBinding{
    enabled true
}
编写测试类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class User {
    private String name;
    private String password;
    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
编写布局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
33
34
35
36
37
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!--第一种引入方式-->
        <variable
            name="user"
            type="com.cj5785.mvvmdesign.User" />
        <!--第二种引入方式-->
        <!--<import type="com.cj5785.mvvmdesign.User" />-->
        <!--<variable-->
        <!--name="user"-->
        <!--type="User" />-->
        <!--如果有多个相同model,可以取别名-->
        <!--<import alias="typeUser" type="com.cj5785.mvvmdesign.User" />-->
        <!--<variable name="user" type="typeUser" />-->
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@{user.name}" /><!--显示字段-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@{user.password}" /><!--显示字段-->
    </LinearLayout>
</layout>
在Activity中调用测试1
2
3
4
5
6
7
8
9
10
11
12
13public class MainActivity extends AppCompatActivity {
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        使用MVVM时候,这里不能使用setContentView
//        setContentView(R.layout.activity_main);
		//ActivityMainBinding在编译时自动生成
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("jack","123456");
        binding.setUser(user);
    }
}
这样就完成了MVVM模型最简单的运用
此时就显示出了设置的name和password
接下来测试数据变化更新UI
修改测试类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
29public class User extends BaseObservable{
    private String name;
    private String password;
    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(BR.password);
    }
}
在Activity中使用延迟设置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MainActivity extends AppCompatActivity {
    User user;
    Handler handler = new Handler();
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        使用MVVM时候,这里不能使用setContentView
//        setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        user = new User("jack", "123456");
        binding.setUser(user);
        handler.postDelayed(new Runnable() {
            
            public void run() {
                user.setName("Bob");
                user.setPassword("654321");
            }
        }, 5000);
    }
}
然后可以看到在经历5秒以后,文字被改变了
使用MVVM框架加载网络图片又怎么做呢
首先导入com.squareup.picasso:picasso依赖
在User中添加相应的图片获取方法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
33
34
35
36
37
38
39
40
41
42
43
44public class User extends BaseObservable{
    private String name;
    private String password;
    private String header;
    public User(String name, String password,String header) {
        this.name = name;
        this.password = password;
        this.header = header;
    }
    ("bind:header")
    public static void getImage(ImageView imageView,String url){
        Picasso.with(imageView.getContext()).load(url).into(imageView);
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }
    
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
        notifyPropertyChanged(BR.password);
    }
    public String getHeader() {
        return header;
    }
    public void setHeader(String header) {
        this.header = header;
    }
}
修改布局文件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
33
34
35
36
37
38
39
40
41
42
43<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <!--第一种引入方式-->
        <variable
            name="user"
            type="com.cj5785.mvvmdesign.User" />
        <!--第二种引入方式-->
        <!--<import type="com.cj5785.mvvmdesign.User" />-->
        <!--<variable-->
        <!--name="user"-->
        <!--type="User" />-->
        <!--如果有多个相同model,可以取别名-->
        <!--<import alias="typeUser" type="com.cj5785.mvvmdesign.User" />-->
        <!--<variable name="user" type="typeUser" />-->
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:header="@{user.header}"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@{user.name}" /><!--显示字段-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@{user.password}" /><!--显示字段-->
    </LinearLayout>
</layout>
在Activity中直接调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MainActivity extends AppCompatActivity {
    User user;
    Handler handler = new Handler();
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        使用MVVM时候,这里不能使用setContentView
//        setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        user = new User("jack", "123456","http://i0.hdslb.com/bfs/archive/42172b20c899bcbbad0d46868af81b8081e999f1.jpg");
        binding.setUser(user);
        handler.postDelayed(new Runnable() {
            
            public void run() {
                user.setName("Bob");
                user.setPassword("654321");
            }
        }, 5000);
    }
}
接下来看看除了使用BaseObservable的另一种方式1
2
3
4public class UserField {
    public ObservableField<String> name = new ObservableField<>();
    public ObservableField<String> password = new ObservableField<>();
}
布局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
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="field"
            type="com.cj5785.mvvmdesign.UserField"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@{field.name}" /><!--显示字段-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@{field.password}" /><!--显示字段-->
    </LinearLayout>
</layout>
调用1
2
3
4
5
6
7
8
9
10
11public class FieldTestActivity extends Activity {
    UserField userField = new UserField();
    
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setField(userField);
        userField.name.set("Alen");
        userField.password.set("121212");
    }
}