DataBinding-MVVM设计模式:
MVVM 可以算是 MVP的升级版,将 Presenter 改名为 ViewModel。关键在于 View和Model的双向绑定,当 View 有用户输入后,ViewModel 通知 Model 更新数据,同理 Model 数据更新后,ViewModel 通知 View 更新。
一 环境要求:
-
Android Studio 版本在 1.3以上
-
Gradle 的版本要在 1.5.0-alpha1 以上
-
需要在 Android SDK manager 中下载 Android Support repository
然后在对应的 Module 的 build.gradle 中添加:
android {
....
dataBinding {
enabled =true
}
}
public class User extends BaseObservable { private String name; private boolean isAdult; @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; } @Bindable public boolean isAdult() { return isAdult; } public void setAdult(boolean adult) { isAdult = adult; } }
创建好对象之后 接下来 就是在xml 设置进去啦
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" > <data> <variable name="user" type="com.example.willer.databindingdemo.data.User"></variable> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" /> </LinearLayout> </layout>
需要值得注意的是,根节点以 layout 定义 而且是不需要设置宽高 属性当哦,节点data 可以认为是数据来源,通过代码中直接设置,简简单单当做完以上两步之后,就可以在代码中使用啦
(3)代码中使用
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); final User user = new User(); user.setName("willer"); user.setAdult(true); binding.setUser(user); }
经过简单三步之后,最简单的databinding使用就已经完成了,是不是很简单呢。 接下来我想说明一些在第一次使用dataBinding中的坑,在刚开使用的时候,始终疑惑,为什么我的代码生成不出 ActivityMainBinding 这个类,即这行代码
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);后来去stackOverflow上查找了一下问题,最后只需要clean project 才生成,希望能帮助遇坑当小伙伴们,这仅仅是遇见当坑之一,稍微说明其他当一些坑。需要说明一下, ActivityMainBinding是自动生成当。
(3)设置点击事件
创建一个对象
public class MyHandlers { public void onClickFriend(View view) { ... } }
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="handlers" type="com.example.Handlers"/> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" android:onClick="@{handlers::onClickFriend}"/> </LinearLayout> </layout>另外一种用法
public class Presenter { public void onSaveClick(Task task){} }
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="task" type="com.android.example.Task" /> <variable name="presenter" type="com.android.example.Presenter" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> </LinearLayout> </layout>
(4)消除空指针
自动生成的 DataBinding 代码会检查 null,避免出现NullPointerException。
例如在表达式中 @{user.phone}如果user == null 那么会为 user.phone 设置 默认值null 而不会导致程序崩溃(基本类型将赋予默认值如 int为0,引用类型都会赋值null)。
跟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>
遇到 相同的类名 的时候:
使用 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}"/>
(6)三元运算
在 User 中添加 boolean类型 的 isStudent属性,用来判断是否为学生:
注意:需要用到双引号的时候,外层的双引号改成单引号。
还可以这样用:
这里用到的 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 是怎么实现图片的加载的。
/** * 使用ImageLoader显示图片 * @param imageView * @param url */ @BindingAdapter({"bind:imageUrl"}) public static void imageLoader(ImageView imageView, String url) { Glide.with(imageView.getContext()).load(url).into(imageView); }需要值得注意当是 上面这个方法必须是静态当 否则会报错
首先要提到 BindingAdapter注解,这里创建了一个类,里面有显示图片的方法:
这里只用了 bind 声明了一个 imageUrl 自定义属性,等下在布局中会用到。
这个类中只有一个静态方法 imageLoader,里面有两参数,一个是需要设置图片的 view,另一个是对应的 Url,这里使用了
Glide库加载图片。
看看它的布局是什么样的吧:
<?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"></variable> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView app:imageUrl="@{imageUrl}" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </layout>注意哦,app这个属性不要忘记导入
xmlns:app="http://schemas.android.com/apk/res-auto"
最后在 MainActivity 中绑定下数据就可以了:binding.setImageUrl("url");
是不是很方便,不用关心他是什么时候调用imageLoader这个静态方法,databinding直接会内部调用
(7)数据的绑定
a)Observable 对象
实现android.databinding.Observable
接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。
Observable
接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable
的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable
注解给getter以及setter内通知来完成的。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getFirstName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
在编译期间,Bindable
注解在BR类文件中生成一个Entry。BR类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable
接口通过方便的PropertyChangeRegistry
来实现用于储存和有效地通知监听器。
b)Observable 字段
一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFields
。ObservableFields
是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
就是这样,要访问该值,使用set和get方法:
user.firstName.set("Google");
int age = user.age.get();
c)Observable 集合
一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap
用于键是引用类型,如String
。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout文件中,通过String键可以访问map:
<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);
在layout文件中,通过索引可以访问list:
<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"/>
后续的内容 下次补充哈