在上一篇博文中我们给出了一个例子,在layout中添加data 节点,并在里面用variable标签声明一个变量,然后在java文件中声明一个对应的变量,将其绑定到layout中。 那接下来的部分我们就继续了解下,可以绑定哪些类型:
在绑定某个类型之前需要将当前类型导入到layout布局中,要实现这个目标必须依赖于import标签:
使用DataBinding在布局中可以导入类: 基本的格式如下:
<data > <import type="android.os.Bundle"/> </data >
这就和Java中导包一样,没特殊的地方: 在类名有冲突的时候,还可以使用别名来区别二者,具体的别名使用方法如下:
<import type ="android.os.Bundle"/> <import type ="com.idealist.testDatabing.Bundle" alias ="selfBundle"/>
基本上包括自定义类型在内的全部类型都可以在layout中声明:
导入的类型还可以在表达式中使用static属性和方法:
<TextView android:text ="@{MyUtils.log(user.lastName)}" android:layout_width ="wrap_content" android:layout_height ="wrap_content" />
像JAVA一样,java.lang.*是自动导入的。
导入包后就可以使用这些包声明对应的变量,变量声明方式如下:
Variables 变量 这个就和变量定义一个样,type指定变量类型,name 指定变量名,对于bean类型必须有get/set方法
<variable name ="name" type ="String" />
当属性名需要用到单引号的时候外面引号需要用双引号,反之,当内部用单引号的时候外部需要用双引号,这个很多语言都有这个用法。
android: text =android: text ="@{map[`firstName`]}"
在变量中还可以用到对应的资源
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding} " android:text="@{@plurals/banana(bananaCount)} "
集合的用法: 集合泛型要特别注意左尖括号需要使用转译:
<data class="CollectionsBinding" > <import type ="java.util.Map" /> <import type ="java.util.List" /> <import type ="android.util.SparseArray" /> <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 >
List 用法:
<TextView android:text ="@{list[index]}" android:layout_width ="wrap_content" android:layout_height ="wrap_content" />
SparseArray 用法:
<TextView android:text ="@{sparse[index]}" android:layout_width ="wrap_content" android:layout_height ="wrap_content" />
Map 用法:
<TextView android:text ="@{map[key]}" android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> <TextView android:text ='@{map["firstName"]}' android:layout_width ="wrap_content" android:layout_height ="wrap_content" /> <TextView android:text ="@{map[`firstName`]}" android:layout_width ="wrap_content" android:layout_height ="wrap_content" />
Include 用法: 注意下面的例子中在name.xml以及contact.xml两个layout文件中必需要有user variable
<?xml version="1.0" encoding="utf-8" ?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:bind ="http://schemas.android.com/apk/res-auto" > <data > <variable name ="user" type ="com.example.User" /> </data > <LinearLayout android:orientation ="vertical" android:layout_width ="match_parent" android:layout_height ="match_parent" > <include layout ="@layout/name" bind:user ="@ {user} " /> <include layout ="@layout/contact" bind:user ="@ {user} " /> </LinearLayout > </layout >
除了上述的用法,目前还支持设置默认值给某个属性
android: text="@{String.valueOf(user.age),default=`defaultValue`}"
除了直接使用变量外还可以使用通过运算符进行运算后的值
支持的运算符:
算术运算符 + - / * % 逻辑运算符 && || 二进制运算符 & | ^ 字符串连接 + 一元运算 + - ! ~ 移位运算 >> >>> << 比较运算 == > < >= <= instanceof null 强制转换 方法调用 数据访问 [] 三元运算 ?: 除了上述基本的运算外下面还有一些特殊的操作符:
?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:
android: text ="@{user.displayName ?? user.lastName}"
* 自定义 Binding 类名称 在上一篇博文中介绍到会自动生成ActivityMainBinding ,这个其实是根据布局来创建的,我们上个例子中布局文件名为activity_main,将当中的下划线去掉然后下划线分割的两端首字母大写,在最后加上Binding 就构成了ActivityMainBinding。但是我们还可以自己指定一个类名:
<data class ="com.idealist.customName" > ... </data > <data class ="CustomBinding" > </data > 在apppackage/databinding下生成CustomBinding;<data class =".CustomBinding" > </data > 在apppackage下生成CustomBinding;<data class ="com.example.CustomBinding" > </data > ` 明确指定包名和类名。
* 实时反馈数据的变化 我们上面介绍的可以将数据和layout上的数据进行绑定,但是当绑定数据变化后并不能立刻反映到View杀害能够。Data Binding的真正强大的地方是当数据变化时,可以通知对应的Data对象。 有三种不同的数据变化通知机制:Observable对象、ObservableFields以及observable Collections。下面就来介绍下这个用法:
继承BaseObservable的方式 实现android.databinding.Observable接口的类可以为绑定对象添加一个监听器用于监听所有你想监听的对象的变化。 要实现上述的绑定可以分成两步: 1 继承BaseObservable的基类来创建Observable对象。 2 为getter添加Bindable注释。
下面是一个例子: 这个和上一篇博文介绍的一样就不多解释了:
<?xml version="1.0" encoding="utf-8" ?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" > <data class = "CustomBindingName" > <import type ="com.idealist.databindingdemo.bean.UserBean" /> <variable name ="user" type ="UserBean" /> </data > <LinearLayout xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context ="com.idealist.databindingdemo.MainActivity" > <TextView android:gravity ="center" android:text ="@ {user.firstName} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <TextView android:gravity ="center" android:text ="@ {user.lastName} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <Button android:text ="Change Name to IOS" android:id ="@+id/changeName" android:layout_width ="match_parent" android:onClick ="onChangeName1" android:layout_height ="wrap_content" /> <Button android:text ="Change Name to Android" android:id ="@+id/changeName1" android:layout_width ="match_parent" android:onClick ="onChangeName2" android:layout_height ="wrap_content" /> </LinearLayout > </layout >
这里的Userbean 继承自BaseObservable并且在对应的get方法上添加了@Bindable注释。并在set方法中添加notifyPropertyChanged(BR.lastName);来通知变化
public class UserBean extends BaseObservable { @Bindable public String getFirstName ( ) { return firstName; } public void setFirstName (String firstName ) { this .firstName = firstName; notifyPropertyChanged (BR .firstName ); } @Bindable public String getLastName ( ) { return lastName; } public void setLastName (String lastName ) { this .lastName = lastName; notifyPropertyChanged (BR .lastName ); } private String firstName; private String lastName; public UserBean (String firstName, String lastName) { this .firstName = firstName; this .lastName = lastName; } }
接着就可以在MainActivity中调用set方法来设置对应的值,一旦bean的值一改变对应TextView 上的文本就立刻改变。 需要注意的是上面的BR是编译阶段生成的一个类,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry。 当数据发生变化时还是需要手动发出通知。 通过调用 notifyPropertyChanged(BR.firstName) 可以通知系统 BR.firstName 这个 entry 的数据已经发生变化,需要更新 UI。
public class MainActivity extends AppCompatActivity { private UserBean userBean = null; @Override protected void onCreate(Bundle savedInstanceState ) { super.onCreate(savedInstanceState ) ; CustomBindingName binding = DataBindingUtil . setContentView(this , R.layout .activity_main ) ; userBean = new UserBean("Android" , "OS" ) ; binding.setUser(userBean ) ; } public void onChangeName1(View view ) { userBean.setFirstName("IOS" ) ; userBean.setLastName("Jobs" ) ; } public void onChangeName2(View view ) { userBean.setFirstName("Android" ) ; userBean.setLastName("OS" ) ; } }
运行结果如下:
ObservableFiled 这种方法也可以实现Observable 对象的效果,它的好处是不用在set方法中添加notifyXXXXX 首先我们在原来例子的基础上添加了一个用于显示年龄的TextView
<?xml version="1.0" encoding="utf-8" ?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" > <data class = "CustomBindingName" > <import type ="com.idealist.databindingdemo.bean.UserBean" /> <variable name ="user" type ="UserBean" /> </data > <LinearLayout xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context ="com.idealist.databindingdemo.MainActivity" > <TextView android:gravity ="center" android:text ="@ {user.firstName} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <TextView android:gravity ="center" android:text ="@ {user.lastName} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <TextView android:gravity ="center" android:text ="@ {String.valueOf(user.age)} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <Button android:text ="Change Name to IOS" android:id ="@+id/changeName" android:layout_width ="match_parent" android:onClick ="onChangeName1" android:layout_height ="wrap_content" /> <Button android:text ="Change Name to Android" android:id ="@+id/changeName1" android:layout_width ="match_parent" android:onClick ="onChangeName2" android:layout_height ="wrap_content" /> </LinearLayout > </layout >
在原来的Bean中添加如下代码:
public ObservableInt age = new ObservableInt ();public ObservableInt getAge ( ) { return age; } public void setAge (ObservableInt age ) { this .age = age; }
除了ObservableInt,还提供了如下的类型,基本上所有的基本类型都提供了:
public ObservableInt age = new ObservableInt ();public ObservableBoolean age1 = new ObservableBoolean ();public ObservableChar age2 = new ObservableChar ();public ObservableByte age3 = new ObservableByte ();public ObservableDouble age4 = new ObservableDouble ();public ObservableFloat age5 = new ObservableFloat ();public ObservableLong age6 = new ObservableLong ();public ObservableShort age7 = new ObservableShort ();public ObservableField<String > age8 = new ObservableField <>();
接下来就是通过如下的设置bean值,一旦bean值被设置,就会立刻反应到View上:
public class MainActivity extends AppCompatActivity { private UserBean userBean = null; @Override protected void onCreate(Bundle savedInstanceState ) { super.onCreate(savedInstanceState ) ; CustomBindingName binding = DataBindingUtil . setContentView(this , R.layout .activity_main ) ; userBean = new UserBean("Android" , "OS" ) ; binding.setUser(userBean ) ; } public void onChangeName1(View view ) { userBean.setFirstName("IOS" ) ; userBean.setLastName("Jobs" ) ; userBean.getAge() .set(23 ); } public void onChangeName2(View view ) { userBean.setFirstName("Android" ) ; userBean.setLastName("OS" ) ; userBean.getAge() .set(26 ); } }
除了上述的基本类型ObServerable 还支持List Map等集合类型:
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" />
* 在DataBinding中使用View的id 在Bind数据后,我们有时候会需要为某个按钮添加一个响应事件,这时候我们可以这样做: 如下布局中有两个按钮,id分别为changeName以及changeName1,我们下面将要为这两个按钮添加点击事件:
<?xml version="1.0" encoding="utf-8" ?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" > <data class = "CustomBindingName" > <import type ="com.idealist.databindingdemo.bean.UserBean" /> <variable name ="user" type ="UserBean" /> </data > <LinearLayout xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context ="com.idealist.databindingdemo.MainActivity" > <TextView android:gravity ="center" android:text ="@ {user.firstName} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <TextView android:gravity ="center" android:text ="@ {user.lastName} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <TextView android:gravity ="center" android:text ="@ {String.valueOf(user.age)} " android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <Button android:text ="Change Name to IOS" android:id ="@+id/changeName" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> <Button android:text ="Change Name to Android" android:id ="@+id/changeName1" android:layout_width ="match_parent" android:layout_height ="wrap_content" /> </LinearLayout > </layout >
下面就是为按钮添加事件的办法,很简单吧感觉比findviewById简单不少:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private UserBean userBean = null; @Override protected void onCreate(Bundle savedInstanceState ) { super.onCreate(savedInstanceState ) ; CustomBindingName binding = DataBindingUtil . setContentView(this , R.layout .activity_main ) ; userBean = new UserBean("Android" , "OS" ) ; binding.setUser(userBean ) ; binding.changeName.setOnClickListener(this ) ; binding.changeName1.setOnClickListener(this ) ; } @Override public void onClick(View v ) { switch (v.getId() ) { case R . id.changeName: userBean.setFirstName("IOS" ) ; userBean.setLastName("Jobs" ) ; userBean.getAge() .set(23 ); break; case R . id.changeName1: userBean.setFirstName("Android" ) ; userBean.setLastName("OS" ) ; userBean.getAge() .set(26 ); break; } } }
Event Binding (事件绑定) 下面例子是实现的是按钮的二值状态切换:
首先定义一个接口
public interface UserPressEvent { void press(View view ); void unPress(View view ); }
布局中使用:
<variable name ="event" type ="com.idealist.UserPressEvent" /> android:onClick ="@{user.isPressed? event.unPress : event.press}"
在Activity实现该接口UserPressEvent:
@Override public void press(View view ) { user .isPressed(true ); } @Override public void unPress(View view ) { user .isPressed(false ); }
自定义转换 有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:
<View android:background ="@{isError ? @color/red : @color/white}" android:layout_width ="wrap_content" android:layout_height ="wrap_content" />
这里,背景需要Drawable对象,但颜色是一个整数。不管何时有Drawable并且返回值是一个整数,那么整数类型会被转换为ColorDrawable。这个转换是通过使用带有BindingConversion注解的静态方法完成的1
@BindingConversion public static ColorDrawable convertColorToDrawable (int color) { return new ColorDrawable(color); }
属性 对于一个属性,Data Binding会试图找到对应的setAttribute方法。 比如下面的例子:
<com.idealist.databinding.view.GithubCard android:layout_width ="match_parent" android:layout_height ="200dp" android:gravity ="center" app:age ="27" app:firstName ="@{@string/firstName}" app:lastName ="@{@string/lastName}" />
这里并没有在declare-styleable 中定义 但是我们在自定义GithubCard中有age,firstName,lastName这些属性的setter方法所以可以直接使用上述的方式进行设置:
下面再来看个在网上找的一个例子,下面先列出代码然后展开分析:
<layout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" > <data > <import type ="com.liangfeizc.databinding.sample.attributesetter.AttributeSettersActivity" /> <variable name ="activity" type ="AttributeSettersActivity" /> <variable name ="imageUrl" type ="String" /> </data > <LinearLayout android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <com.liangfeizc.avatarview.AvatarView android:layout_width ="match_parent" android:layout_height ="wrap_content" app:error ="@ {@drawable/error} " app:imageUrl ="@ {imageUrl} " app:onClickListener ="@ {activity.avatarClickListener} " /> </LinearLayout > </layout >
public class AttributeSettersActivity extends BaseActivity { private ActivityAttributeSettersBinding mBinding; public View.OnClickListener avatarClickListener = new View.OnClickListener() { @Override public void onClick(View v ) { Toast . makeText(AttributeSettersActivity.this , "Come on" , Toast.LENGTH_SHORT) .show() ; mBinding.setImageUrl(Randoms.nextImgUrl () ); } }; @Override protected void onCreate(Bundle savedInstanceState ) { super.onCreate(savedInstanceState ) ; mBinding = DataBindingUtil . setContentView(this , R.layout .activity_attribute_setters ) ; mBinding.setActivity(this ) ; mBinding.setImageUrl(Randoms.nextImgUrl () ); } @BindingAdapter({"bind:imageUrl" , "bind:error" }) public static void loadImage(ImageView view , String url , Drawable error ) { Log . d(App.TAG, "load image" ); Picasso .with (view.getContext() ).load(url).error(error).into(view); } }
首先在AttributeSettersActivity onCreate方法中建立DataBinding后会将Activity,和ImageUri传到布局上, 我们在AttributeSettersActivity中看到如下方法:
@BindingAdapter({"bind:imageUrl", "bind:error"}) public static void loadImage(ImageView view , String url, Drawable error) { Log .d(App.TAG, "load image"); Picasso.with (view .getContext()).load (url).error(error).into (view ); }
这个是用来干嘛的呢?它的意思是: 如果AvatarView中的 imageUrl和 error 参数都存在并且由于imageUrl是string类型error是drawable 类型则就会调用上面定义的绑定适配器,并将对应的参数传入。在匹配适配器的时候,会忽略自定义的命名空间。