要讲到mvvm
模式的开发,就不得不提DataBinding
。两者结合才是一个完整体。
关于使用,直接官网:Data Binding Guide
对应的中文翻译:Data Binding 用户指南(Android)
一个github
项目,精通DataBinding
的所有用法:MasteringAndroidDataBinding
一般使用
使用bingding
针对对应view
的id
进行设置。
需要注意的是,布局
xml
中的根布局必须为layout
,否则编译器无法生成对应的binding
.
public class ViewWithIDsActivity extends BaseActivity {
ActivityViewWithIdsBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_with_ids);
}
public void showMyName(View view) {
binding.firstName.setText("liang");
}
}
可以看到我们可以直接使用binding.firstName
来对指定的view
进行操作,再也不需要使用findViewbyId
或者其他注解的方式了。
??
判断语句
android:text="@{user.displayName ?? user.lastName}"
代替:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
alias
引用同名的时候,进行区分,相当于别名:
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
另一种情况:*如果import
的类为静态类(通常为工具类会用到),我们并不能使用
<variable name="user" type="DetailUser" />
因为这条标签name="user"
相当生成一个对象了,那么这个对象并不能引用静态类的方法,我应该直接使用就好了:
<import type="com.liangfeizc.library.utils.MyStringUtils" />
<import type="com.liangfeizc.databinding.utils.MyStringUtils" alias="StringUtils" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{StringUtils.capitalize(user.firstName)}" />
自定义生成的binding
,通常系统会根据xml
名称生成对应的驼峰的binding,当然我们可以自定义生成:
<data class=".ContractBinding">
...
</data>
集合的使用
主要分两种情况:一种是以数组的形式,如:
List<String>
,另一种是以链表的形式,如:map<String,String>
在xml
中,要使用<
来代替<
在variable
中声明,具体如下:
以数组为原型的集合使用:
<variable name="list" type="List<String>" />
<TextView
android:text="@{list[index]}"/>
以map
为原型的键值的集合使用(比较多变,都是一个意思吧)
<variable name="map" type="Map<String, String>"/>
<TextView
android:text='@{map["firstName"]}'/>
<TextView
android:text="@{map[`firstName`]}"/>
<TextView
android:text="@{map["firstName"]}" />
引用资源resource
- 格式化字符串(可使用正则表达式等)
android:text="@{@string/nameFormat(firstName, lastName)}"
- 设置间距
padding
,理所当然会联想到margin
,当时经过试验并不支持。现仅支持padding
,包括paddingLeft
等。
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
单向绑定-Observable
的使用。
这里需要理解两个概念:单向绑定与双向绑定。果然之前没理解的东西,终会到某一天由于这个概念的模糊,浪费一大堆时间后,才真正理解这个概念。--单向绑定与双向绑定。
双向绑定
通过搜索这个概念的时候,第一条的就是AngularJS
的实现。其确切指的是:
双向数据绑定指的是将对象属性变化绑定到UI,或者反之。换句话说,如果我们有一个拥有name属性的user对象,当我们给user.name赋予一个新值是UI也会相应的显示新的名字。同样的,如果UI包括了一个输入字段用来输入用户名,输入一个新的值会导致user对象中的那么属性发生变化。
引用:谈谈JavaScript中的双向数据绑定
简单来讲就是:数据改变会引用UI的改变,相反:UI的数据改变,也会影响数据的改变。最突出的实例就是:EditText
的使用
而遗憾的是:Android
到目前为止,并不支持双向绑定,仅支持单向绑定->数据的变化更新到UI的变化而已。
比如你给
EditText
绑定了一个String
,该String
的变化会及时显示在EditText
,但是你在EditText
中输入的内容不会直接赋值到String
中。
引用:Android DataBinding实践
我们通过使用Observable
的接口来实现单向绑定。这里有几种形式:
- 继承
BaseObservable
+Bindable
注解+notifyPropertyChanged(BR.xx)
public class ObservableUser extends BaseObservable {
private String mFirstName;
@Bindable
public String getFirstName() {
return mFirstName;
}
public void setFirstName(String firstName) {
mFirstName = firstName;
notifyPropertyChanged(BR.firstName);
}
}
注意当使用@Bindable
注解后,对应的BR.xx
就会自动生成。
- 继承
BaseObservable
+notifyChange();
public class TestUser extends BaseObservable{
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyChange();
}
}
这个比上一个简单多了。不用注解,直接notifyChange()
搞定。
- 使用
ObservableField<T>
T
代表任何数据类型,或者对应ObservableBoolean, ObservableByte
类。
另外一点:定义的属性必须为public final
,那么久不用get/set
的声明了。
public class PlainUser {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
- 最后是使用
map、list
集合
需要继承
ObservableMap<K, V>
接口。提供了对应实现类ObservableArrayMap<K, V>
集合。或者ObservableArrayList
的list集合。
扩展:自定义实现一个双向绑定
参考来源:Two-way Android Data Binding
How to use two-way Data Binding to manage a layout
其demo的地址9databinding
不过一般看这个代码片段就可以明白了:TwoWayBoundString.java
我经过实战对应的代码:
@BindingAdapter(value = "bind:textChange")
public static void bindEditTest(final EditText view, final QuantityViewModel viewModel){
if(view.getTag(R.id.et_goos_amount)==null){//防止多次进行监听的问题,使用tag进行缓存
view.setTag(R.id.et_goos_amount,true);
view.addTextChangedListener(new TextWatcher() {
...
@Override
public void afterTextChanged(Editable s) {
viewModel.onEditTextChange(s);//对editText修改后进行对应的改变。
}
});
}
String newValue=viewModel.getNumber().get();
if(!view.getText().toString().equals(newValue)){
view.setText(newValue);
}
}
# 对应的xml,注意不需要对android:text进行绑定数据了
<EditText
android:id="@+id/et_goos_amount"
bind:textChange="@{quantity}"/>
注意:不需要对android:text进行绑定数据了
更新于6.22:
今天发现原来系统已经内置了一个TextViewBindingAdapter
已经考虑到了双向绑定的实现,源码
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
"android:afterTextChanged"}, requireAll = false)
public static void setTextWatcher(TextView view, final BeforeTextChanged before,
final OnTextChanged on, final AfterTextChanged after) {
那么我们只需要如下操作即可:
public TextViewBindingAdapter.AfterTextChanged getAfterTextChanged() {
return new TextViewBindingAdapter.AfterTextChanged() {
@Override
public void afterTextChanged(Editable s) {
remarks = s.toString();
}
};
}
<EditText
...
android:afterTextChanged="@{data.quantityViewModel.getAfterTextChanged()}"
...
android:text="@{data.quantityViewModel.number}"/>
在最新的2016 google i/o
中已经支持了双向绑定了:
很简单,在要使用双向绑定的地方,使用 “@={}” 即可。
<EditText android:text="@={user.firstName}" />
具体查看: 安卓 Data Binding 使用方法总结(妹妹篇)
针对ViewStub
的使用
通过DataBindingUtil.bind(view)
进行绑定。
mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("liang", "fei");
binding.setUser(user);
}
});
对应的xml布局:
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
难点
- 引入
include
标签的布局的时候,此时我们并不能通过binding.xx
来获取include
标签布局的元素,执行对应的操作。
没办法我们只能通过findViewById()
的方法来得多对应的view
。庆幸的是databinding也提供另一种替代方式。
<data>
<variable
name="contact"
type="com.liangfeizc.databinding.model.Contact" />
</data>
<include
layout="@layout/contact"
bind:contact="@{contact}" />
当然,注意@layout/contact
的布局是使用contact
进行绑定的。
而且,我们使用的是bind:contact
来进行对include
的解决。
高级的用法
使用@BindingAdapter
:
使用注解,直接关联xml
上的绑定:
@BindingAdapter({"bind:imageUrl", "bind:default"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
这里使用的attr
为bind:xxx
.默认的声明控件为:xmlns:bind="http://schemas.android.com/tools"
。
上面的意思为:当检测到xml
上有bind:imageUrl、bind:error
的使用,会在加载的使用自动调用:loadImage
方法,注意是static+@BindingAdapter
的注解。下面是使用:
<data>
<variable
name="data"
type="com.qoshop.xshop.entity.Goods" />
</data>
<ImageView
android:id="@+id/iv_image"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_146"
bind:default="@{@drawable/ic_goods_default}"
bind:src="@{data.img}" />
其中:bind:default
的资源是drawable
,而bind:src
为网络的url
的string
链接。注意:里面的值的数据类型的区别。
使用databinding
来代替Attribute setters
.
有了 Data Binding,即使属性没有在
declare-styleable
中定义,我们也可以通过 xml 进行赋值操作。
在xml
中的使用:
<com.liangfeizc.databinding.view.NameCard
...
app:age="27"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"/>
为此,使用非android:
的属性标签的时候,需要如下的定义:
<declare-styleable name="NameCard">
<attr name="age" format="integer" />
</declare-styleable>
但使用databinding
的话,可以省去这个的定义。详细看:NameCard
其实,我们只需要对应对firstName、lastName
进行设定就好。就像之前使用类的对应绑定那样。如下
public void setFirstName( final String firstName) {mFirstName.setText(firstName);}
public void setLastName( final String lastName) {mLastName.setText(lastName);}
@bindingConversion
自定义转换
通常我们在
xml
中使用的android:background
为drawable
资源,如果想使用color
的资源,可以使用@bindingConversion
进行设定。
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
对应的activity
的设置为
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}