Android中MVVM模式
来自: http://my.oschina.net/u/1175007/blog/613889
官方文档地址:
https://developer.android.com/intl/zh-cn/tools/data-binding/guide.html
Data Binding Library有什么用?
每当有新东西出现我们都喜欢问有什么用?这可不是然并卵的东西,我们来了解下。
1、是官方支持的MVVM模式框架
2、可以直接在布局 xml 文件中绑定数据,无需再 findViewById 然后手工设置数据
3、可以提高解析XML的速度
4、UI与功能的解耦合
一、环境
在开始使用新东西之前,我们需要稍微的配置一下环境,这里要求你的Android Studio版本是1.3+,使用eclipse的同学暂时还没有办法使用该框架,请换用Android Studio。还有,在开始之前,请更新你的Support repository到最新的版本。
万事俱备,那我们就开始搭配环境!
新建一个project,在dependencies中添加以下依赖
dependencies { classpath "com.android.tools.build:gradle:1.3.0" classpath "com.android.databinding:dataBinder:1.0-rc1" }
由于依赖的项目在 jcenter 服务器中,所以在repositories 中需要添加 jcenter如下:
allprojects { repositories { jcenter() } }
在需要使用支持库的module 的build.gradle文件中添加插件申请:
apply plugin: 'com.android.application' apply plugin: 'com.android.databinding'
例子:现在做一个点击一下按钮然后年龄会+1的小功能,这里也模拟了我们是数据更新。
第一步:创建XML布局
创建一个布局xml文件,就像以前一样。然后这里我们需要做一些少少的修改,在这个框架下我们的思维要稍稍改变一下了,以前的布局XML只描述了布局,他是相对固定的东西,在Data Binding Library下我们的布局XML就像是一个类,他可以有变量也能进行一定的运算。其实Data Binding Library还真的给你生成了一个类似这样的类。
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="cn.golditfin.bean.User" /> <variable name="buttonname" type="String" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" /> <TextView android:id="@+id/age_textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:text="@{String.valueOf(user.age)}" /> <Button android:id="@+id/age_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:text="@{buttonname}" /> </RelativeLayout> </layout>
其中我们布局文件的根节点变成了layout,然后定义了变量。
在此布局定义了:
1、两个变量,一个是对象User,一个是String类型的buttonname。
2、两个TextView一个Button
<data> <variable name="user" type="cn.golditfin.bean.User"/> </data>
在 data 元素中使用 variable 来声明在布局文件中使用的变量。
name声明了变量的名称,type声明了变量的类型,变量可以为基本类型如int,也可以为集合或者对象。
View中使用变量用@{} 格式来调用,下面Textview使用了我们对象user的变量name。
<TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" />
第二步:定义数据对象
public class User { public String name; public int age; public User(String name,int age){ this.name = name; this.age = age; } }
很简单就两个成员变量
然后是Java代码调用。
第三步:绑定数据
public class MainActivity extends AppCompatActivity { private User myUser; private ActivityMainBinding myBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); myBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); myUser = new User("年龄",4); myBinding.setUser(myUser); myBinding.setButtonname("年龄+1"); myBinding.ageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myUser.age++; myBinding.setUser(myUser); } }); } }
在Activity的onCreate方法中进行了基本的数据绑定。一句一句代码给你们解释。
1、原来设置布局的setContentView方法我们现在不用了,改成了DataBindingUtil.setContentView
2、对于第一点的返回值是框架给我们生成的类,该类按照单词首字母大写的规则,布局的名字加上Binding组成。比如我们的布局是activity_main.xml,所以按照这个规则来转换的话就变成了ActivityMainBinding。
3、自动生成的类ActivityMainBinding其实就是代表了那个布局,里面包括了布局的View,我们声明的变量。我们可以通过这个对象获取到布局的元素。
4、binding.setUser(user);没错,这里的setUser方法是框架给我们自动生成的,每一个布局中声明的变量都会自动生成对应的get,set方法。
好的,现在我们跑起来看看。
现在已经实现我们的想法了,但是看到这里的代码以及我们实现的过程遇到数据改变不能刷新问题。
当我尝试单方面的进行myUser对象数据更改的时候界面上的数据并不能自动刷新,然后我通过setUser再次设置就可以刷新了,这简直是烦恼啊!
带着这个问题我们来进一步的学习Data Binding Library。
解决问题
数据刷新自动同步如何完成呢?
解决数据同步其实谷歌已经想到了,在这里提供了两个解决办法。
1、让实体类继承BaseObservable类
public class User extends BaseObservable{ public String name; public int age; public User(String name,int age){ this.name = name; this.age = age; } @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.user); } @Bindable public int getAge() { return age; } public void setAge(int age) { this.age = age; notifyPropertyChanged(BR.user); } }
说明:
1、首先继承BaseObservable类
2、给成员变量的get方法添加@Bindable注解
3、在成员变量的set方法的最后调用方法notifyPropertyChanged,其中里面的参数是在BR类中记录的变量,那些变量就是我们在XML中声明的那些啦,类似于安卓资源管理的R.java文件,不过这里管理的是布局XML的变量,是自动生成的。
在这里我又有问题要问了,之前我的实体类这么简约,现在变这么长,感觉不爽!
那么有没有简约版的呢,因为我很懒不想弄这么多代码?
2、使用Observable数据类型
这是一个简约的方法,谷歌为我们提供了基于常用数据类型的Observable类型,就是我们熟悉的数据类型前面加上Observable组成。
例如:
Int类型对应ObservableInt
Boolean类型对应ObservableBoolean
其他基本类型类似以上写法。
而String类型对应的是ObservableField<String>,ObservableField是一个泛型
我们可以看看他的源码
然后再看看ObservableInt的源码
其他类型的源码基本类似。这样看来其实ObservableField除了可以存放对象还可以代替其他所有基本类型来使用。
public class User { public ObservableField<String> name = new ObservableField<>(); public ObservableInt age = new ObservableInt(); public User(String name,int age){ this.name.set(name); this.age.set(age); } }
以上就是进过改造的实体类。
使用Observable数据类型之后数据是通过get,set方法来获取和改变的。
在XML布局中使用时跟原来的数据类似一样。
public class MainActivity extends AppCompatActivity { private User myUser; private ActivityMainBinding myBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); myUser = new User("年龄",4); myBinding.setUser(myUser); myBinding.setButtonname("年龄+1"); myBinding.ageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { myUser.age.set(myUser.age.get()+1); } }); } }