标签:buffer arrays 修改 静态绑定 hang uil creat viewgroup 简单
Data Binding 解决了 Android UI 编程的一个痛点,官方原生支持 MVVM 模型可以让我们在不改变既有代码框架的前提下,非常容易地使用这些新特性。
Data Binding 框架如果能够推广开来,也许 RoboGuice、ButterKnife 这样的依赖注入框架会慢慢失去市场,因为在 Java 代码中直接使用 View 变量的情况会越来越少。
android {
dataBinding {
enabled true
}
}
【编写布局文件】
使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 ViewGroup,而是变成了 layout,并且新增了一个节点 data。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data> … </data> //新增的data节点
<LinearLayout> ... </LinearLayout> //原先的根节点
</layout>
要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View) 进行绑定,data 节点的作用就像一个桥梁,搭建了 View 和 Model 之间的通路。
定义一个 POJO 类:
public class User {
private String firstName;//就是一个简单的Java Bean
private String lastName;
...
}
稍后,我们会在 xml 布局文件的 data 节点中声明一个 User 类型的 variable,这个变量会为 UI 元素提供数据,然后在 Java 代码中把『后台』数据与这个 variable 进行绑定。
回到布局文件,在 data 节点中声明一个 User 类型的变量 user。
<data>
<variable name="user" type="com.bqt.basic.User" />
</data>
其中 type 属性就是我们刚刚定义的 User 类。
当然,data 节点也支持 import,并且 import 并没有要求一定要放在使用前,所以上面的代码可以这样写:
<data>
<import type="com.bqt.basic.User" />
<variable name="user" type="User" />
</data>
注意:
我们 build 工程后会自动在 build 目录下生成一个继承自 ViewDataBinding 的类,这个类将被放置在databinding包下。比如,如果我们的包名是com.bqt.databinding,那么它将被放置在com.bqt.databinding.databinding包下。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//用 DatabindingUtil.setContentView() 来替换掉 setContentView()
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
...
}
【Java代码中绑定variable】
在创建**Binding类的实例后,我们便可以使用它来绑定variable,例如:
User user = new User("Test", "User");
binding.setUser(user);
注意,所有的 set/get 方法都是根据 variable 名称生成的,例如,上面布局中定义了一个 name="user" 的变量:
那么就会生成对应的 set/get 方法:
public void setUser(com.bqt.databinding.model.User User) { ... }
public com.bqt.databinding.model.User getUser() { ... }
【布局中使用variable】
数据与variable 绑定之后,通过 @{} 可以直接把 Java 中定义的属性值赋值给 xml 中 UI 元素的某个属性。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
至此,一个简单的数据绑定就完成了。
任何Plain old Java object(POJO)可用于Data Binding,但修改POJO不会导致UI更新。比如在上面我们的案例中,我们通过 binding.setUser(user) 将数据 User 和 View 绑定在了一起,我们本想着,在 User 改变之后, View 会自动更新,但实际上是没有这种效果的。
Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable对象、ObservableFields以及Observable集合。当这些可观察Data对象绑定到UI,Data对象属性的更改后,UI也将自动更新。
实现 android.databinding.Observable 接口的类可以允许添加一个监听器到 Bound 对象以便监听对象上的所有属性的变化。Observable 接口有一个机制可以添加和删除监听器,但通知与否由开发人员管理。
为了使开发更容易,一个 BaseObservable 的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个@Bindable注解给getter以及setter内通知来完成的。
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with @Bindable to generate a field in BR to be used as fieldId.
* @param fieldId The generated BR id for the Bindable field.
*/
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) return;//private transient PropertyChangeRegistry mCallbacks;
}
mCallbacks.notifyCallbacks(this, fieldId, null);//通过【PropertyChangeRegistry】来实现通知监听器
}
public class ObservableUser extends BaseObservable {
private String name;
@Bindable//给getter方法添加注解。The getter for an observable property should be annotated with Bindable.
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);//通知某个属性改变了。Notifies listeners that a specific property has changed.
}
}
BR 是编译阶段生成的一个类,功能与 R.java 类似。
在编译期间,通过@Bindable注解标记的getter方法返回的字段会在BR类文件中生成一个同名的Entry,如下:
package com.bqt.databinding;
public class BR {
...
public static final int name = 17;
...
}
除此之外,还有一种更细粒度的绑定方式,可以具体到成员变量,这种方式无需继承 BaseObservable,一个简单的 POJO 就可以实现。
系统为我们提供了所有的原始类型所对应的 Observable 类,例如ObservableInt、ObservableFloat、ObservableBoolean等等,还有一个 ObservableField 对应着 引用类型。这种方式非常适合那些几乎没有几个属性的 POJO 。
public class android.databinding.ObservableInt extends BaseObservable implements Parcelable Serializable
ObservableInt:An observable class that holds a primitive int.
ObservableField:An object wrapper to make it observable.
Observable field classes may be used instead of creating an Observable object.
Fields of this type should be declared final because bindings only detect检测 changes in the field‘s value, not of the field itself.
This class is parcelable可扩展的 and serializable but callbacks are ignored when the object is parcelled / serialized. Unless you add custom callbacks, this will not be an issue because data binding framework always re-registers callbacks when the view is bound.
要使用它需要在data对象中创建public final字段,如:
public class PlainUser {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
要访问该值,使用set和get方法:
PlainUser plainUser = new PlainUser();
plainUser.name.set("包青天,ObservableField");
plainUser.age.set(27);
int age = plainUser.age.get();
一些app使用更多的动态结构来保存数据,Observable集合允许键控访问这些data对象。
【ObservableArrayMap】
public class android.databinding.ObservableArrayMap<K, V> extends ArrayMap<K, V> implements ObservableMap<K, V>
ObservableArrayMap用于键是引用类型,如String。
ObservableArrayMap<String, Object> mapUser = new ObservableArrayMap<>();
mapUser.put("name", "ObservableArrayMap");
mapUser.put("age", 27);
在layout文件中,通过String键可以访问map。
android:text=‘@{mapUser["name"]}‘
android:text=‘@{String.valueOf(1 + (Integer)mapUser["age"])}‘
注意,在XML布局的某个属性值中使用泛型时,要用【<】来代替【<】
public class android.databinding.ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T>
ObservableArrayList<Object> listUser = new ObservableArrayList<>();
listUser.add("ObservableArrayList");
listUser.add(17);
listUser.set(0, "包青天,ObservableArrayList");
listUser.set(1, 27);
在layout文件中,通过索引可以访问list:
<import type="android.databinding.ObservableList"/>
<variable name="listUser" type="ObservableList<Object>"/>
android:text=‘@{listUser[0]}‘
android:text=‘@{String.valueOf(1 + (Integer)listUser[1])}‘
在布局中引用variable时,将之前的【@{}】改成了【@={}】即可实现双向绑定,比如通过【@={}】将一个变量和EditText的内容绑定在一起,当用户更改EditText中的内容时,和它绑定的变量也会同步改变。
android:text="@{user.name}"//通过【 @{user.name} 】方式绑定时,当用户更改EditText中的内容时,和它绑定的变量【不会】同步改变
android:text="@={user2.name}"//通过【 @={user.name} 】方式绑定时,当用户更改EditText中的内容时,和它绑定的变量【会】同步改变
常用表达式跟Java表达式很像,以下这些是一样的:
缺少的操作:
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName=‘@{"image_" + id}‘
常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用 [] 来访问。
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
android:text="@{list[index]}"
android:text="@{sparse[index]}"
android:text="@{map[key]}"
注意,在XML布局的某个属性值中使用泛型时,要用转义字符【<】来代替【<】,否则编译失败:
> org.xml.sax.SAXParseException; 与元素类型 "variable" 相关联的 "type" 属性值不能包含 ‘<‘ 字符。
可以使用单引号包含属性值,而在表达式中使用双引号:
android:text=‘@{map["name"]}‘
也可以使用双引号来包含属性值,而在字符串前后使用【`】,这个是ESC下面、Tab上面的那个键:
android:text="@{map[`name`]}"
android:text="@{map["name"]}"
也可以使用双引号来包含属性值,而在字符串前后使用转义字符【"】:
android:text="@{map["name"]}"
PS,XML中需要的转义字符:
&(逻辑与) &
<(小于) <
>(大于) >
"(双引号) "
‘(单引号) '
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
//dimens.xml中的定义
<dimen name="largePadding">20dp</dimen>
android:padding="@{large ? (int)@dimen/largePadding : (int)@dimen/smallPadding}" //错误写法
android:text="@{@string/nameFormat(firstName, lastName)}"
<string name="nameFormat">Full Name: %1$s %2$s</string> //格式化字符串
android:text="@{MyStringUtils.upper(user.firstName)}"
android:text="@{User.SEX}"
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
<data class=".CustomBinding"> ... //放在package的根目录,即上述databinding的父目录
<data class="com.mypackage.CustomBinding"> ... //提供整个包名
【Null合并操作】
??运算符:左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:
android:text="@{user.displayName ?? user.lastName}"
//等价于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
【带 ID 的 View】
只要给 View 定义一个 ID,Data Binding 就会为我们生成一个对应的 final 字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用findViewById还要快。例如:
android:id="@+id/firstName"
binding.firstName.setText("包");//firstName是DataBinding自动生成一个对应的变量
属性中的 variable 名字从容器 layout 中传递到被包含的 layout。
注意:在include的layout文件中定义的variable必须在外部也要定义,比如下例中,在 user.xml 中必需要有 user variable。
<include layout="@layout/layout_input"/>
<include
layout="@layout/user"
bind:user="@{user}"/>
【避免 NullPointerException】
Data Binding代码生成时自动检查是否为nulls来避免出现NullPointerException错误。
例如,在表达式 @{user.name} 中,如果user是null,则 user.name 会赋予它的默认值(null)。如果你引用 user.age(age是int类型),那么它的默认值是0。
【直接Binding】
当一个variable或observable变化时,binding会在下一帧之前被计划要改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用executePendingBindings()方法。
Evaluates评估 the pending bindings, updating any Views that have expressions bound to modified variables. This must be run on the UI thread.
【后台线程】
只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。
xml 文件与之前的代码一样,根节点改为 layout,在 LinearLayout 中添加一个 ViewStub,添加 ID。
<ViewStub
android:id="@+id/m_view_stub"
android:layout="@layout/m_view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
编译后会在 Binding 中生成同名的 ViewStubProy 类型的成员:
public final android.databinding.ViewStubProxy mViewStub;
在 Java 代码中,通过 Binding 实例为 ViewStubProy 注册 ViewStub.OnInflateListener 事件,当监听到ViewStub的OnInflateListener事件时需要为新的布局创建一个Binding:
mBinding.mViewStub.setOnInflateListener(new ViewStub.OnInflateListener() {//监听ViewStub的OnInflateListener监听器
@Override
public void onInflate(ViewStub stub, View inflated) {//当载入另一个layout时为新的布局创建一个Binding
MViewStubBinding binding = DataBindingUtil.bind(inflated);//同样,此Binding的名字取决于ViewStub布局的名字。
User user = new User("fee", "lang");
binding.setUser(user);//此Binding只能处理ViewStub布局中带id的View或变量,而不能处理根布局中的东西
}
});
//填充ViewStub
if (!mBinding.mViewStub.isInflated()) mBinding.mViewStub.getViewStub().inflate();
static class UserAdapter2 extends RecyclerView.Adapter<UserAdapter2.UserHolder> {
private List<User> mUsers;
public UserAdapter2(List<User> mUsers) {
this.mUsers = mUsers;
}
static class UserHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public UserHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public int getItemCount() {
return mUsers.size();
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),
R.layout.user_item, viewGroup, false);//【在 onCreateViewHolder 的时候创建这个 DataBinding】
UserHolder holder = new UserHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
ViewDataBinding binding = holder.getBinding();//【在 onBindViewHolder 中获取这个 DataBinding】
binding.setVariable(BR.user, mUsers.get(position));
binding.executePendingBindings();
}
}
还有另外一种比较简洁的方式,直接在构造 Holder 时把 View 与自动生成的 XXXBinding 进行绑定。
static class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private List<User> mUsers;
public UserAdapter(List<User> mUsers) {
this.mUsers = mUsers;
}
static class UserHolder extends RecyclerView.ViewHolder {//最主要的区别就是在这里!
private UserItemBinding mBinding;
public UserHolder(View itemView) {
super(itemView);
mBinding = DataBindingUtil.bind(itemView);//【在构造 Holder 时把 itemView 与自动生成的 XXXBinding 进行绑定】
}
public void bind(User user) {
mBinding.setUser(user);//绑定数据
}
}
@Override
public int getItemCount() {
return mUsers.size();
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.user_item, viewGroup, false);
return new UserHolder(itemView);
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
holder.bind(mUsers.get(position));//只需要为每一个item绑定数据,而不需要手动操作item中的UI
}
}
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
同样,对于自定义View,即使属性没有在 declare-styleable 中定义,我们也可以通过 xml 进行赋值操作。
为了演示这个功能,我自定义了一个 View - NameCard,属性资源 R.styleable.NameCard 中只定义了一个 age 属性:
<resources>
<declare-styleable name="NameCard">
<attr name="age" format="integer" />
</declare-styleable>
</resources>
其中 firstName 和 lastName 有对应的两个 setter 方法(前提条件):
public class NameCard extends LinearLayout {
private TextView mFirstName, mLastName;
public void setFirstName(@NonNull final String firstName) {
mFirstName.setText(firstName);
}
public void setLastName(@NonNull final String lastName) {
mLastName.setText(lastName);
}
public void setAge(@IntRange(from = 1) int age) {
mAge = age;
}
...
}
只要有 setter 方法就可以像下面代码一样赋值:
<com.bqt.databinding.NameCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:onClickListener="@{activity.clickListener}"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"
app:age="27" />
onClickListener 也是同样道理(因为任何View都有对应的setOnClickListener方法),只不过我们是在 Activity 中定义了一个 Listener。
@Target(ElementType.ANNOTATION_TYPE)
public @interface BindingMethod {
Class type();//The View Class that the attribute is associated with.
String attribute();//The attribute to rename. Use android: namespace for all android attributes or no namespace for application attributes.
String method();//The method to call to set the attribute value.
}
@Target({ElementType.TYPE})
public @interface BindingMethods {
BindingMethod[] value();
}
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
@BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
...
})
public class TextViewBindingAdapter { ... }
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();//The attributes associated with this binding adapter.
boolean requireAll() default true;
}
@BindingAdapter("android:bufferType")
public static void setBufferType(TextView view, TextView.BufferType bufferType) {
view.setText(view.getText(), bufferType);
}
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) view.removeOnLayoutChangeListener(oldValue);//先移除旧的
if (newValue != null) view.addOnLayoutChangeListener(newValue);//再添加新的
}
}
@BindingAdapter({"android:onClick", "android:clickable"})
public static void setOnClick(View view, View.OnClickListener clickListener, boolean clickable) {
view.setOnClickListener(clickListener);
view.setClickable(clickable);
}
public class TextViewBindingAdapter {
@BindingAdapter({"android:bufferType"})
public static void setBufferType(TextView view, TextView.BufferType bufferType) {
view.setText(view.getText(), bufferType);
}
private static void setIntrinsicBounds(Drawable drawable) {
if (drawable != null) drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
}
@BindingAdapter({"android:drawableBottom"})
public static void setDrawableBottom(TextView view, Drawable drawable) {
setIntrinsicBounds(drawable);//设置边界
Drawable[] drawables = view.getCompoundDrawables();
view.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawable);//修改DrawableBottom
}
}
<variable name="pt" type="int"/>
android:paddingTop="@{pt}"
@BindingAdapter({"android:paddingTop"})
public static void setPaddingTopAndBottom(View view, int paddingTB) {//当设置paddingTop时,同时设置paddingTop和paddingBottom
view.setPadding(view.getPaddingLeft(), paddingTB, view.getPaddingRight(), paddingTB);
}
mBinding.setPt(10 * new Random().nextInt(10));
<com.liangfeizc.avatarview.AvatarView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:error="@{@drawable/error}"
app:imageUrl="@{imageUrl}"
app:onClickListener="@{activity.mClickListener}"/>
@BindingAdapter({"bind:imageUrl", "error"})//自定义 namespaces 将被忽略
public static void loadBqtImage(ImageView view, String url, Drawable error) {//方法名随意,但参数必须和注解中指定的属性一一对应
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
Error:(37, 21) 警告: Application namespace for attribute bind:imageUrl will be ignored.
<TextView
android:text=‘@{userMap["lastName"]}‘
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@Target({ElementType.METHOD})
public @interface BindingConversion {
}
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
/**
* !!! Binding conversion should be forbidden, otherwise it will conflict with{@code android:visiblity} attribute.
*/
@BindingConversion
public static int convertColorToString(int color) {
switch (color) {
case Color.RED:
return R.string.red;
case Color.WHITE:
return R.string.white;
}
return R.string.app_name;
}
2017-9-27
标签:buffer arrays 修改 静态绑定 hang uil creat viewgroup 简单
原文地址:http://www.cnblogs.com/baiqiantao/p/7602999.html