android的mvvm模式

简介

官方文档

m:model
v:view(Activity)
vm:viewModel
一个view要和一个viewModel绑定

我认为就是通过view层直接操作viewModel,通过ObservableInt等基本类型或者ObservableField直接绑定layout上的值

准备工作

先在app的build.gradle中打开dataBinding

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

布局文件在原本布局的基础上增加根布局layout,并加入data标签,在data标签中增加要给variable,指定一个type,就是viewmodel类,name是这个类在这个xml文件中的名字,下面调用viewmodel的时候都用vm调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<data>
<variable
name="vm1"
type="cn.xwmdream.news.ViewModel"/>
</data>
<!--原本布局-->
<LinearLayout>
<TextView
android:text="@{vm1.textt}"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>

activity中加载布局的方式也变了

1
2
3
4
5
6
7
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
final ViewModel viewModel = new ViewModel(this);
binding.setVm1(viewModel);
}

先用binding绑定布局,然后创建一个viewmodel和binding绑定,这个viewmodel会传入布局文件中当成上面指定的vm1

ViewModel

1
2
3
public class ViewModel {
public ObservableField<String> textt = new ObservableField<String>("helloworld");
}

这个viewmodel中有一个textt的String类型的属性,和上面布局中的一个textview绑定,当这个textt的值发生改变时,xml中那个textview的text值也会发生改变

直接写一个例子,mvvm的RecyclerView

activity_main.xml

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
44
45
<?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"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity">
<data>
<variable
name="vm1"
type="cn.xwmdream.news.ViewModel"/>
</data>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
app:setback="@{vm1.color}"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/bar_zone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</android.support.design.widget.AppBarLayout>
<TextView
android:id="@+id/tv_errormessage"
android:visibility="@{vm1.errorVisibility}"
android:text="@string/error"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:setRefreshing="@{vm1.refer}"
android:OnRefreshListener="@{vm1.refresh}"
android:visibility="@{vm1.listVisibility}"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.design.widget.CoordinatorLayout>
</layout>

MainActivity.java

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
44
45
46
47
48
package cn.xwmdream.news;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import java.util.ArrayList;
import java.util.List;
import cn.xwmdream.news.databinding.ActivityMainBinding;

/**
* @author Administrator
*/
public class MainActivity extends AppCompatActivity implements ViewModel.DataListener {

private List<NewBean> itemsBeanList;
private int page = 1;
private static final String TAG = "MainAcclivity";
private NewAdapter adapter;
private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
final ViewModel viewModel = new ViewModel(this);
binding.setVm1(viewModel);

page = 0;
//通过调用binding的绑定界面的id操作控件
setSupportActionBar(binding.toolbar);

binding.rvRecycler.setLayoutManager(new GridLayoutManager(this, 2));
//创建适配器
itemsBeanList = new ArrayList<NewBean>();
adapter = new NewAdapter(this,itemsBeanList);
//加载适配器
binding.rvRecycler.setAdapter(adapter);
viewModel.getmess(page++);
}

@Override
public void updatedata(List<NewBean> newdata) {
int count = itemsBeanList.size();
int newcount = newdata.size();
itemsBeanList.addAll(newdata);
adapter.notifyItemRangeChanged(count + 1, newcount);
}
}

ViewModel.java

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
44
45
46
47
48
49
50
51
52
53
54
55
56
package cn.xwmdream.news;

import android.databinding.BindingAdapter;
import android.databinding.ObservableBoolean;
import android.databinding.ObservableField;
import android.databinding.ObservableInt;
import android.graphics.Color;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.View;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ViewModel {
public ObservableInt errorVisibility = new ObservableInt(View.GONE);
public ObservableInt listVisibility = new ObservableInt(View.VISIBLE);
public ObservableBoolean refer = new ObservableBoolean(false);
public ObservableField<String> color = new ObservableField<String>("#000000");
private List<NewBean> mlist;
private DataListener listener;
public ViewModel(DataListener listener){
this.listener=listener;
}
public void getmess(int i){
if (mlist == null) {
mlist = new ArrayList<NewBean>();
}
mlist.clear();
for (int j = i; j < i+10; j++) {
mlist.add(new NewBean(String.format("标题%d",j),String.format("内容%d",j)));
}
listener.updatedata(mlist);
}
public SwipeRefreshLayout.OnRefreshListener refresh(){
return new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refer.set(true);
Random random = new Random();
getmess(random.nextInt(100));
refer.set(false);
}
};

}
//让view层实现的接口,用户向view层发送数据
public interface DataListener{
void updatedata(List<NewBean> list);
}
@BindingAdapter("app:setback")
public static void setbackk(CoordinatorLayout layout,ObservableField<String> color){
layout.setBackgroundColor(Color.parseColor(color.get()));
}
}

NewBean.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.xwmdream.news;

public class NewBean {
private String title;
private String content;
public NewBean(String title, String content) {
this.title = title;
this.content = content;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

news_title_item.xml

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
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="vm"
type="cn.xwmdream.news.ItemViewModel" />
</data>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:id="@+id/cardview"
android:onClick="@{vm.onmyclick}"
app:cardCornerRadius="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/item_jian_ju"
android:orientation="vertical">
<!--因为有getTitle方法,所以可以直接写title-->
<TextView
android:id="@+id/tv_itemtitle"
android:layout_width="match_parent"
android:text="@{vm.title}"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_itemcontent"
android:text="@{vm.content}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v7.widget.CardView>
</layout>

NewAdapter.java

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
44
45
46
47
package cn.xwmdream.news;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import java.util.List;
import cn.xwmdream.news.databinding.NewsTitleItemBinding;
public class NewAdapter extends RecyclerView.Adapter<NewAdapter.ViewHolder>{
private Context context;
private List<NewBean> mItemList;

@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
NewsTitleItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context),R.layout.news_title_item,viewGroup,false);
final ViewHolder holder = new ViewHolder(binding);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
NewBean itemsBean = mItemList.get(i);
viewHolder.bindData(itemsBean);
}
@Override
public int getItemCount() {
return mItemList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder{
NewsTitleItemBinding binding;
public ViewHolder(NewsTitleItemBinding binding) {
super(binding.cardview);
this.binding=binding;
}
public void bindData(NewBean newBean){
if (binding.getVm() == null) {
binding.setVm(new ItemViewModel(newBean));
}else{
binding.getVm().setBean(newBean);
}
}
}
public NewAdapter(Context context, List<NewBean> newBeanList){
this.context=context;
mItemList = newBeanList;
}
}

ItemViewModel.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.xwmdream.news;
import android.view.View;
import android.widget.Toast;
public class ItemViewModel {
private NewBean nb;
public ItemViewModel(NewBean newBean){
nb=newBean;
}
public void onmyclick(View view){
Toast.makeText(view.getContext(), nb.getTitle(), Toast.LENGTH_SHORT).show();
}
public String getTitle(){
return nb.getTitle();
}
public String getContent(){
return nb.getContent();
}

public void setBean(NewBean newBean) {
this.nb=newBean;
}
}

学到的点

  • 这个程序是用了mvvm模式,其中有两个view(主页面和item页面),和两个viewmodel
  • layout中可以指定属性是viewmodel的值,如activity_main.xml的27行,错误信息的textview的visibility就和viewmodel的errorVisibility值发生绑定,当viewmodel的errorvisibility值改变了,那layout中相应的属性也就发生变化
  • 当binding绑定布局后可以直接使用binding.id操作布局上的控件,如MainActivity的第30行,直接操作id为toolbar的控件
  • activity_main.xml第三十行是一个SwipeRefreshLayout,他有一个监听方法,OnRefreshListener,可以使用34行的操作给他绑定这个监听方法,相当于原来findviewbyid().OnRefreshListener(),直接给他设置了一个监听,是viewmodel的那个方法,同样33行也是,本来是一个setRefreshing(boolean)的一个方法,直接在xml文件中绑定了里面的值,后续直接修改那个值就相当于原来调用的setRefreshing
  • activity_main.xml的第13行,直接在xml文件中指定了一个app:setback的属性,同时在viewmodel中第52行使用@BindingAdapter({“setback”})绑定了这个setback这个方法,在xml文件中传入了一个值,这样的话在创建页面的时候就会调用vm的那个方法,传入的第一个参数是这个空间,第二个参数是传入的值,这个方法必须是静态无返回值的,而且参数必须是vm中定义的

  • news_title_item.xml第14行直接设置了点击事件,对应ItemViewModel.java第九行

  • NewAdapter.java第16行那个NewsTitleItemBinding是因为他拉取的文件名叫news_title_item.xml