hello android databinding!
0x00 修改gradle文件
1 2 3 4 5 6 7 8 9 buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.5.0' classpath "com.android.databinding:dataBinder:1.+" } }
1 2 apply plugin: 'com.android.application' apply plugin: 'com.android.databinding'
0x01 格式 在原有的布局文件中,加上一层layout
标签包裹住,需要放置的数据,则放在data
标签里。
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" > <data > <variable name ="field" type ="com.example.type" /> </data > <LinearLayout /> </layout >
note: 截止至2016-04-01
,在目前的Android Studio IDE1.5.1
以及dataBinder1.0-rc5
版本中,还没有办法做到xml文件里,data
标签里,包名,类名的自动补全 。
0x02 数据绑定和事件绑定
layout.xml
如下,对于type
属性,如果该类属于java.lang
包的话,可以省去包名,如String
,Integer
等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android ="http://schemas.android.com/apk/res/android" > <data > <variable name ="user" type ="com.example.User" /> <variable name ="listener" type ="com.example.Listener" /> </data > <LinearLayout android:layout_width ="fill_parent" android:layout_height ="fill_parent" > <TextView android:layout_width ="fill_parent" android:layout_height ="fill_parent" android:text ="@{user.name}" /> <Button android:layout_width ="fill_parent" android:layout_height ="fill_parent" android:onClick ="@{listener.onClick}" /> </LinearLayout > </layout >
1 2 3 4 5 6 public class User { private final String name; public User (String name) { this .name = name; } }
com.example.Listener.java
如下
1 2 3 4 public class Listener { void onClick (View view) ; }
每一个使用dataBinding的layout文件,都会被编译产生一个对应的类,这个类的位置在build/intermediates/classes/debug/packageName/databinding/xxxBinding.java
中,并且遵循驼峰命名法
如 main_activity.xml –> MainActivityBinding.java
其中带有ID的控件,都会对应生成一个变量,同样遵循驼峰命名法
如 btn_register –> btnRegister
基于<variable name="user" type="com.example.User"/>
这样子的声明,生成的xxxBinding.java
会有一个setUser(User user)
方法。
在Activity里这样子调用和绑定
1 2 3 4 5 6 7 8 9 @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); MainActivityBinding binding = DataBindingUtil.setContentView(this , R.layout.main_activity); User user = new User("Test" , "User" ); binding.setUser(user); binding.btnRegister.setText("xxx" ); }
在fragment中,可以在onCreateView()
方法中进行数据绑定
1 2 3 4 5 6 7 @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FragmentLoginBinding dataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false ); return dataBinding.getRoot(); }
0x03 BindAdapter的使用 对于非控件自带的attr,使用BindAdapter注解来实现,以一个根据URL加载头像
的场景为例。
写一个ViewModel
1 2 3 4 5 6 7 public class ImageViewModel { @BindAdapter("bind:url") public static final void loadAvatar (ImageView img, String avatarUrl) { loadAvatarByUrl(img, url); } }
在布局文件中加入注解中绑定的url属性(需要加入appNS)
1 2 3 4 5 6 <ImageView xmlns:app ="http://schemas.android.com/apk/res-auto" android:layout_width ="wrap_content" android:layout_height ="wrap_content" app:url ="@{user.avatarUrl}" />
再也不用在每一个需要用到头像的地方,都写类似下面的代码了。
虽然loadAvatarByUrl(img, url)
的逻辑可能并不复杂,但是使用DataBinding胜在将加载头像的逻辑完全从Activity或者Fragment中剥离出来
。
1 2 ImageView img = (ImageView)findViewById(R.id.img); loadAvatarByUrl(img, url);
0x04 DataBinding在RecyclerView的使用 dataBinding对RecyclerView
的支持也非常好,用法很简便。
首先在原来的item布局文件中,加入<layout>
和<data>
等标签。
接着,在ViewHolder中声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyViewHolder extends RecyclerView .ViewHolder { private MyItemBinding binding; public ExprHolder (View itemView) { super (itemView); binding = DataBindingUtil.bind(itemView); } public MyItemBinding getBinding () { return binding; } }
在onCreateViewHolder
回调方法中,将LayoutInflater
加载进来的View
作为构造函数传递给ViewHolder
1 2 3 4 5 6 @Override public MyViewHolder onCreateViewHolder (ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item, parent, false ); return new MyViewHolder(view); }
在onBindViewHolder
回调方法中,完成数据绑定
1 2 3 4 5 @Override public void onBindViewHolder (MyViewHolder holder, int position) { Object obj = Objects.get(position); holder.getBinding().setObj(obj); }
0x05 其他用法
1 2 3 4 <TextView android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="@{user.name ?? user.phone ?? User.NO_LOGIN}" />
原生代码对比:
1 2 3 4 5 6 7 8 9 10 TextView tv = (TextView)findViewById(R.id.tv); if ("" .equals(user.getName())) { if ("" .equals(user.getPhone())) { tv.setText(User.NO_LOGIN); return ; } tv.setText(user.getPhone()); return ; } tv.setText(user.getName());
使用import
标签引用一个包含有静态方法的辅助类
1 2 3 4 5 6 7 8 9 10 <data > <variable name = "mInteger" type = "int" /> <import type = "com.xxx.XxxHelper" /> </data > <TextView android:layout_width ="fill_parent" android:layout_height ="fill_parent" android:visibility ="@{XxxHelper.hasPermisson(mInteger) ? View.VISIBLE:View.GONE}" />
1 2 3 4 5 6 7 8 9 10 11 <data > <variable name ="mode" type ="int" > <import type ="com.config.Constants" /> </data > <Button android:id ="@+id/btn_register" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:text ="@{mode == Constants.MODE_REGISTER? @string/btn_register : @string/reset_password}" />
com.config.Constants
这样子写
1 2 3 4 public class Constants { public static final int MODE_REGISTER = 0 ; public static final int MODE_RESET_PASSWORD = 1 ; }
别名 如果import
标签引用到的两个类名相同,可以使用alias
属性
1 2 3 4 <data > <import type ="com.package.one.Klass" > <import type ="com.package.two.Klass" alias ="KlassTwo" > </data >
0x06 开发过程中遇到的坑
在写xml文件的时候,由于插件的支持有限,有关dataBinding这一部分没有自动补全和提示,有时候编译的时候报错说xxxDataBinding类不存在
之类的信息,一定是手抖写错了表达式,只要将Android Studio的message
窗口(就是那个查看编译反馈的窗口)拉到最下面,一般都可以看到是哪一个布局文件中表达式写错了,修改对应的表达式即可。
由于dataBinding插件是实时编译的,有时候会出现以下情况,修改了某个布局文件的名字,或者其中控件的ID,也会报错,这时候只要找到对应生成的java文件删除,让插件自动重新编译即可。对应的java文件位置在:build/intermediates/classes/debug/packageName/databinding/xxxBinding.java
,或者删除整个build
文件夹也可。