设计模式笔记之三:Android DataBinding库(MVVM设计模式)

本博客转自郭霖公众号:http://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650236908&idx=1&sn=9e53f42e18a81795ef0cfe6fe3959ec2&scene=24&srcid=0910cK3vXJpNzY0CO28i1Qhs#wechat_redirecthtml

 


什么是MVVM

说到DataBinding,就有必要先提起MVVM设计模式。
Model–View–ViewModel(MVVM) 是一个软件架构设计模式,相比MVVM,你们对MVC或MVP可能会更加熟悉。java

  • MVC:(VIew-Model-Controller)
    早期将VIew、Model、Controller代码块进行划分,使得程序大部分分离,下降耦合。
  • MVP:(VIew-Model-Presenter)因为MVC中View和Model之间的依赖太强,致使Activity中的代码过于臃肿。为了他们能够绝对独立的存在,慢慢演化出了MVP。在MVP中View并不直接使用Model,它们之间的通讯是经过 Presenter (MVC中的Controller)来进行的。
  • MVVM:(Model–View–ViewModel)
    MVVM能够算是MVP的升级版,将 Presenter 更名为 ViewModel。关键在于View和Model的双向绑定,当View有用户输入后,ViewModel通知Model更新数据,同理Model数据更新后,ViewModel通知View更新。

Data Binding

在Google I/O 2015上,伴随着Android M预览版发布的Data Binding兼容函数库。
不知道要扯什么了,仍是直接上代码,来看看Data Binding的魅力吧。android

  • 环境要求

    Data Binding对使用的环境仍是有必定要求的(这货有点挑)
    Android Studio版本在1.3以上
    gradle的版本要在1.5.0-alpha1以上
    须要在Android SDK manager中下载Android Support repository
    而后在对应的Module的build.gradle中添加git

    android {
      ....
      dataBinding {
          enabled =true } }

    Gradle须要升级版本的能够参考升级Gradle版本github

  • 建立对象

    建立一个User类swift

    public class User { private String firstName; private String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } }
  • 布局

    在activity_main.xml中布局设计模式

    <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="com.example.gavin.databindingtest.User"/> <variable name="user" type="User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:textSize="20sp" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:textSize="25sp" /> </LinearLayout> </layout>

    这里跟平时的布局有点不一样,最外层是layout,里面分别是是data以及咱们的布局。
    data:声明了须要用到的user对象,type用因而定路径。
    能够在TextView中的看到android:text="@{user.firstName}", 这是什么鬼,没见过这么写的!!!
    (不急,继续往下看)数组

  • 绑定数据

    看看下面的MainActivity网络

    public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Micheal", "Jack"); binding.setUser(user); } }

    问我ActivityMainBinding哪来的?我怎么知道...
    ActivityMainBinding是根据布局文件的名字生成的,在后面加了Binding。
    运行下看看效果吧架构


    效果

有点懵逼了,就绑定了下而已,这些数据是怎么显示到界面上的。


懵逼

他是怎么工做的?
原来Data Binding 在程序代码正在编译的时候,找到全部它须要的信息。而后经过语法来解析这些表达式,最后生成一个类。
经过反编译咱们能够看到,Data Binding为咱们生成了databinding包,以及ActivityMainBinding类(反编译能够参考这里


 


看看咱们在onCreate中最后调用的binding.setUser(user),在ActivityMainBinding中能够看到这个方法。


setUser方法


我想就是这个 super.requestRebind()对数据进行了绑定,至于里面怎么实现的,有待进一步研究。

更多用法

上面只是用一个简单的例子,展现了Data Binding的用法,若是想在实际项目中使用,可不是上面这例子能够搞定的。下面就来讲说Data Bindig的更多用法。

  • 首先消除下你们对空指针的顾虑

    自动生成的 DataBinding 代码会检查null,避免出现NullPointerException。
    例如在表达式中@{user.phone}若是user == null 那么会为user.phone设置默认值null而不会致使程序崩溃(基本类型将赋予默认值如int为0,引用类型都会赋值null)
  • 自定义DataBinding名

    若是不喜欢自动生成的Data Binding名,咱们能够本身来定义
    <data class="MainBinding"> .... </data>
    class对应的就是生成的Data Binding名
  • 导包

    跟Java中的用法类似,布局文件中支持import的使用,原来的代码是这样

    <data> <variable name="user" type="com.example.gavin.databindingtest.User" /> </data>

    使用import后能够写成这样:

    <data> <import type="com.example.gavin.databindingtest.User"/> <variable name="user" type="User" /> </data>

    -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    遇到相同的类名的时候:

    <data> <import type="com.example.gavin.databindingtest.User" alias="User"/> <import type="com.example.gavin.mc.User" alias="mcUser"/> <variable name="user" type="User"/> <variable name="mcUser" type="mcUser"/> </data>

    使用alias设置别名,这样user对应的就是com.example.gavin.databindingtest.User,mcUser就对应com.example.gavin.mc.User,而后

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/>

    -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    当须要用到一些包时,在Java中能够自动导包,不过在布局文件中就没有这么方便了。须要使用import导入这些包,才能使用。如,须要用到View的时候

    <data> <import type="android.view.View"/> </data> ... <TextView ... android:visibility="@{user.isStudent ? View.VISIBLE : View.GONE}" />

    注意只要是在Java中须要导入包的类,这边都须要导入,如:Map、ArrayList等,不过java.lang包里的类是能够不用导包的

  • 表达式

    在布局中,不只可使用

    android:text="@{user.lastName}"

    还可使用表达式如:

    三元运算

    在User中添加boolean类型的isStudent属性,用来判断是否为学生。

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{user.isStudent? "Student": "Other"}' android:textSize="30sp"/>

    注意须要用到双引号的时候,外层的双引号改为单引号。
    还能够这样用

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="学生" android:visibility="@{user.isStudent ? View.VISIBLE : View.GONE}" android:textSize="30sp"/>

    这里用到的View须要在data中声明

    <data> <import type="android.view.View"/> </data>

    注意:android:visibility="@{user.isStudent ? View.VISIBLE : View.GONE}",可能会被标记成红色,不用管它编译会经过的

    ??

    除了经常使用的操做法,另外还提供了一个 null 的合并运算符号 ??,这是一个三目运算符的简便写法。

    contact.lastName ?? contact.name

    至关于

    contact.lastName != null ? contact.lastName : contact.name

    所支持的操做符以下:
    数学运算符 + - / * %
    字符串拼接 +
    逻辑运算 && ||
    二进制运算 & | ^
    一元运算符 + - ! ~
    位运算符 >> >>> <<
    比较运算符 == > < >= <=
    instanceof
    Grouping ()
    文字 - character, String, numeric, null
    类型转换 cast
    方法调用 methods call
    字段使用 field access
    数组使用 [] Arrary access
    三元运算符 ? :

  • 显示图片

    除了文字的设置,网络图片的显示也是咱们经常使用的。来看看Data Binding是怎么实现图片的加载的。
    首先要提到BindingAdapter注解,这里建立了一个类,里面有显示图片的方法。

    public class ImageUtil { /** * 使用ImageLoader显示图片 * @param imageView * @param url */ @BindingAdapter({"bind:image"}) public static void imageLoader(ImageView imageView, String url) { ImageLoader.getInstance().displayImage(url, imageView); } }

    (这方法必须是public static的,不然会报错)
    这里只用了bind声明了一个image自定义属性,等下在布局中会用到。
    这个类中只有一个静态方法imageLoader,里面有两参数,一个是须要设置图片的view,另外一个是对应的Url,这里使用了ImageLoader库加载图片。
    看看吧它的布局是什么样的吧

    <?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="imageUrl" type="String"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" app:image = "@{imageUrl}"/> </LinearLayout> </layout>

    最后在MainActivity中绑定下数据就能够了

    binding.setImageUrl(
      "http://115.159.198.162:3000/posts/57355a92d9ca741017a28375/1467250338739.jpg");

    哇靠!!!就这样?我都没看出来它是怎么设置这些图片的。
    无论了,先看看效果。(其中的原理之后慢慢唠,这里就负责说明怎么使用,这篇已经够长了,不想再写了)


    看个美女压压惊

    使用BindingAdapter的时候,我这还出现了这样的提示,不过不影响运行。不知道大家会不会...


     


    【已解决】
    感谢颜路同窗指出@BindingAdapter({"bind:image"}) 改为
@BindingAdapter({"image"}) 就不会有警告了

  • 点击事件

    在MainActivity中声明方法:

    //参数View必须有,必须是public,参数View不能改为对应的控件,只能是View,不然编译不经过 public void onClick(View view) { Toast.makeText(this,"点击事件", Toast.LENGTH_LONG).show(); }

    布局中:

    <data> ... <variable name="mainActivity" type="com.example.gavin.databindingtest.MainActivity"/> </data> .... <Button ... android:onClick="@{mainActivity.onClick}" />

    最后记得在MainActivity中调用

    binding.setMainActivity(this);

    (发现:布局文件中,variable中的name,在binding中都会生成一个对应的set方法,如:setMainActivity。有set方法,那就应该有get方法,试试getMainActivity,还真有)
    运行下看看效果


    点击事件


    固然若是你不想吧点击事件写在MainActivity中,你把它单独写在一个类里面:

    public class MyHandler { public void onClick(View view) { Toast.makeText(view.getContext(), "点击事件", Toast.LENGTH_LONG).show(); } }
    <data> ... <variable name="handle" type="com.example.gavin.databindingtest.MyHandler"/> </data> .... <Button ... android:onClick="@{handle.onClick}" /> </data>

    在MainActivity调用

    binding.setHandle(new MyHandler());
  • 调用Activity中的变量

    上面看到它调用MainActivity中的onClick方法,那么能够调用MainActivity中的属性吗?
    在MainActivity中定义mName,
    public static String mName = "MM";
    布局中
    <data> ... <variable name="mainActivity" type="com.example.gavin.databindingtest.MainActivity"/> </data> <Button ... android:text="@{mainActivity.mName}" />
    注意这个变量必须是public static
  • 数据改变时更新UI

    当数据发生变化时,咱们能够这样更新UI
    private ActivityMainBinding binding; private User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); user = new User("Micheal", "Jack"); binding.setUser(user); binding.setHandle(new MyHandler()); delay(); } /** * 两秒后改变firstName */ private void delay() { new Handler().postDelayed(new Runnable() { @Override public void run() { user.setFirstName("Com"); binding.setUser(user); } }, 2000); }
    看看调用的这个setUser是什么:

    setUser

    从反编译的代码中能够看出,setUser方法中从新绑定了数据。
    看下效果

效果
  • BaseObservable

    使用上面的代码实现了UI的更新你就知足了?其实官方为咱们提供了更加简便的方式,使User继承BaseObservable,代码以下

    public class User extends BaseObservable { private String firstName; private String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Bindable public String getFirstName() { return this.firstName; } @Bindable public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName); } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); } }

    只要user发生变化,就能达到改变UI的效果。在MainActivity中只要调用如下代码

    user.setFirstName("Com");

    有了BaseObservable就够了?不不不,我比较懒,不想写那么多@Bindable和notifyPropertyChanged。万一里面有几十个属性,那不写哭起来?并且还有可能写丢了。
    Data Binding的开发者贴心得为咱们准备了一系列的ObservableField,包括: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat,ObservableDouble, 以及 ObservableParcelable看看它们的用法
    ObservableField的使用
    一、建立User2

    public class User2 { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); public final ObservableBoolean isStudent = new ObservableBoolean(); }

    这类里面没有Get/Set。
    二、布局文件

    <TextView ... android:text="@{user2.firstName}" /> <TextView ... android:text="@{user2.lastName}" /> <TextView ... android:text="@{String.valueOf(user2.age)}" />

    三、MainActivity中

    mUser2 = new User2(); binding.setUser2(mUser2); mUser2.firstName.set("Mr"); mUser2.lastName.set("Bean"); mUser2.age.set(20); mUser2.isStudent.set(false);

    这里new了一个User2对象后,直接就绑定了。以后只要mUser2中的数据发生变化,UI也会随之更新。
    除了这几个Map跟List也是必不可少的,Data Binding为咱们提供了 ObservableArrayMapObservableArrayList
    ObservableArrayMap的使用

    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);
    <data>
      <import type="android.databinding.ObservableMap"/> <variable name="user" type="ObservableMap<String, Object>"/> </data> … <TextView android:text='@{user["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text='@{String.valueOf(1 + (Integer)user["age"])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>

    ObservableArrayList的使用

    ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17);
    <data> <import type="android.databinding.ObservableList"/> <import type="com.example.my.app.Fields"/> <variable name="user" type="ObservableList<Object>"/> </data> … <TextView android:text='@{user[Fields.LAST_NAME]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>

    在布局中使用中文时,编译没法经过。

    android:text='@{user2.isStudent?"学生":"非学生"}'

    感谢吕檀溪同窗的解决方案:
    这是java环境的问题,在系统环境变量中增长一个变量,变量名为: JAVA_TOOL_OPTIONS, 变量值为:-Dfile.encoding=UTF-8,保存。要重启一次电脑,中文就解决了,可是在某些地方,编译的时候控制台会出现部分乱

  • 在RecyclerView或ListView中使用

    前面说了那么多基础的用法,可仍是不能达到咱们的需求。几乎在每一个app中都有列表的存在,RecyclerView或ListView,从上面所说的彷佛还看不出Data Binding在RecyclerView或ListView中是否也能起做用。(用屁股想也知道,Google的开发团对怎么可能会犯这么低级的错误)。下面以RecyclerView为例子:
    一、直接看Item的布局(user_item.xml):

    <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user2" type="com.example.gavin.databindingtest.User2" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user2.firstName}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="·"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user2.lastName}"/> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{user2.age+""}'/> </LinearLayout> </layout>

    二、RecyclerView的数据绑定是在Adapter中完成的,下面看看Adapter(这里使用了一个Adapter,若是你在使用的时候发现RecyclerView的动画没了,去这里寻找答案)

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> { private List<User2> mData = new ArrayList<>(); public MyAdapter(List<User2> data) { this.mData = data; } @Override public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { return MyHolder.create(LayoutInflater.from(parent.getContext()), parent); } @Override public void onBindViewHolder(MyHolder holder, int position) { holder.bindTo(mData.get(position)); } @Override public int getItemCount() { if (mData == null) return 0; return mData.size(); } static class MyHolder extends RecyclerView.ViewHolder { private UserItemBinding mBinding; static MyHolder create(LayoutInflater inflater, ViewGroup parent) { UserItemBinding binding = UserItemBinding.inflate(inflater, parent, false); return new MyHolder(binding); } private MyHolder(UserItemBinding binding) { super(binding.getRoot()); this.mBinding = binding; } public void bindTo(User2 user) { mBinding.setUser2(user); mBinding.executePendingBindings(); } } }

    三、最后在布局和MainActivity中的使用跟平时的用法同样
    布局中加入RecyclerView:

    <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/>

    MainActivity中:

    List<User2> data = new ArrayList<>(); for (int i = 0; i < 20; i++) { User2 user2 = new User2(); user2.age.set(30); user2.firstName.set("Micheal " + i); user2.lastName.set("Jack " + i); data.add(user2); } RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(new MyAdapter(data));

    这样就能够了。
    不过,在自动生成的ActivityMainBinding中,咱们能够看到根据RecyclerView的id,会自动生成一个recyclerView。


     


    因此在MainActivity中,咱们能够不用findViewById,直接使用binding.recyclerView。

    LinearLayoutManager layoutManager = new LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false); binding.recyclerView.setLayoutManager(layoutManager); binding.recyclerView.setAdapter(new MyAdapter(data));

    来看看效果吧:


    RecyclerView

Tips:

  • tip1:若须要显示int类型,须要加上"":如

    user.age为int类型,须要这样用

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{""+user.age}'/>

    或者

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(user.age)}"/>
  • tip2:不建议新手使用,出现错误的时候根据提示,不容易找到出错位置。(是根本找不到...)

参考

Google官方(权威,不过全英文。点击事件写的好像不对,后来去其余地方查的):
Realm(十分全面):
CSDN-亓斌(有点像google文档的翻译版,总体结果类似):
阳春面的博客(好奇怪的名字)

源码地址https://github.com/Gavin-ZYX/DataBindingTest



文/带心情去旅行(简书做者) 原文连接:http://www.jianshu.com/p/5dcdc5798d85 著做权归做者全部,转载请联系做者得到受权,并标注“简书做者”。
相关文章
相关标签/搜索