数据绑定, 更简单的方式给View赋值和刷新;
软件环境:
android版本: 兼容Android 2.1以上(API level 7+);
Gradle版本: 1.5.0-alpha1 以上;
Android Studio版本: 1.3以上;
配置
app model里的 build.gradle 添加;
1 2 3 4 5 6 |
android { .... dataBinding { enabled = true } } |
如果程序使用的library项目里有data binding, app model 也还是要像上面这样配置;
正文
data(填充的数据)
给View提供数据;
Observable
通知更新;
BaseObservable,ObservableInt,ObservableFloat,ObservableBoolean,ObservableField;
@Bindable 修饰的布尔值不要用 is 开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import android.databinding.BaseObservable; import android.databinding.Bindable; import com.android.databinding.library.baseAdapters.BR; /** * 继承BaseObservable实现数据的刷新 */ public class User extends BaseObservable { // BR 是一个自动生成的文件,类似 R 文件; // @Bindable 注解可以在BR中生成对应常量, 用这个注解修饰变量和get方法; private @Bindable String age; private String name; public User(String name) { this.name = name; } @Bindable public String getName() { return name; } public void setName(String name) { this.name = name; // 通知数据变化 notifyPropertyChanged(BR.name); } } |
布局文件
- 使用 layout 作为根标签,layout 里只有两个子标签,一个 data 另一个是布局;
- 将原有的布局放在layout标签内;
- data 标签设置数据
data标签
java.lang.* 包中的类会被自动导入,可以直接使用,不需要显式import;
data 标签内可以使用:
- import 导入类,既可以是系统的,也可以是自己的
- type属性: 指定该类的路径
- alias属性:导入多个包,如果类名有重复, 可用这个定义 别名
- variable 声明一个数据对象
- type属性: 路径
- name属性: 变量名
@{ }句法
使用@{},{}里的值给属性赋值, {}里放表达式求值或一个由data对象提供的数据;
- ??: ?? 前面的值为空,就使用??后面的值;@{user.name ?? user.age}
- {}里可以直接使用资源(@dimen/…)
- {} 可以使用一些运算符
xml 布局文件中部分符号需要转义
1 2 3 4 5 |
&& && < < > > " " ' ' |
控件使用id
一般流程给控件添加id后, 在java代码中通过 "ViewDataBinding 对象.id值" 访问对象;
activitydatabind.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?xml version="1.0" encoding="utf-8"?> <!--根布局的 layout 没有定位这些信息--> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <!--布局xml会自动生成一个 ViewDataBinding 的继承类,命名为-布局文件名去掉下划线,每个单词首字母大写,结尾加上Binding的形式 如 activity_data_bind 会自动生成一个 ActivityDataBindBinding 类--> <data> <import type="mxguo.databind.MyUtil" /> <variable name="user" type="mxguo.databind.User" /> </data> <!--这里是显示的子布局--> <LinearLayout android:id="@+id/activity_data_bind" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" /> <!--通过 ActivityDataBindBinding.button访问--> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="改变显示" /> </LinearLayout> </layout> |
java代码中数据绑定
ViewDataBinding
1: 使用了 DataBind 的布局会自动生成ViewDataBinding的继承类,继承类命名以"布局名称+Binding";
2:会根据布局中data标签里的variable标签的name属性生成一个set方法。类对象调用set方法绑定数据;
1 2 3 4 5 6 7 8 9 10 11 12 |
// 实例化 Binding DataBindingUtil.setContentView(Activity activity, int layoutId); DataBindingUtil.inflate(LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent, boolean attachToParent); DataBindingUtil.inflate( LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent, boolean attachToParent, DataBindingComponent bindingComponent) ViewDataBinding继承类.inflate(inflater, parent, false); |
activity中使用的例子
使用 DataBindingUtil.setContentView 方法替代 setContentView 方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class DataBindActivity extends AppCompatActivity { private static final String TAG="DataBindActivity"; private ActivityDataBindBinding binding;// 布局名称 activity_data_bind private User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_data_bind); binding= DataBindingUtil.setContentView( this, R.layout.activity_data_bind); user=new User("name"); //绑定数据 binding.setUser(user); binding.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.e(TAG,"onClick"); // 更新数据,自动刷新界面(User 里调用了notifyPropertyChanged) user.setName(Math.random()+""); } }); } } |
在RecycleView里使用
在 item 的布局里添加 data 标签;
把 DataBinding 的一个实例放到 ViewHolder里, 在onCreateViewHolder里存入, 在onBindViewHolder时赋值data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import mxguo.uitext.databinding.RecyItemBinding; public class DataBindAdapt extends RecyclerView.Adapter<DataBindAdapt.ViewHolder> { private ArrayList<User> users=new ArrayList<>(5); public DataBindAdapt(){ //users=new ArrayList<>(10); for (int i=0;i<5;i++){ users.add(new User("name"+i)); } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //在 onCreateViewHolder 生成ViewHolder return ViewHolder.create(LayoutInflater.from(parent.getContext()),parent); } @Override public void onBindViewHolder(ViewHolder holder, final int position) { // 在 onBindViewHolder 里绑定数据 holder.bindTo(users.get(position)); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.e("adapt",""+position); } }); } @Override public int getItemCount() { return users.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { // 生成 binding static ViewHolder create(LayoutInflater inflater, ViewGroup parent) { RecyItemBinding binding = RecyItemBinding .inflate(inflater, parent, false); return new ViewHolder(binding); } private RecyItemBinding mBinding; private ViewHolder(RecyItemBinding binding) { super(binding.getRoot()); mBinding = binding; } public void bindTo(User user) { mBinding.setUser(user); // 如果数据源变化了, 有时候不会立即刷新, 这个方法强制立即刷新,ui线程执行 mBinding.executePendingBindings(); } public RecyItemBinding getBinding(){ return mBinding; } } } |
Custom Setters
使用自定义方法, 接收xml中控件属性里的值;
使用@BindingAdapter注解, ()里放置需要的属性, 多个属性放在{}里;
第一个参数是view;下面的参数和注解里的一致(个数以及出现顺序);
@BindingAdapter 修饰的这个方法可以写在任意位置, 编译时自动处理;
1 2 3 4 |
@BindingAdapter({"bind:imageUrl", "bind:error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.with(view.getContext()).load(url).error(error).into(view); } |
1 2 |
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}"/> |
布局中的控件要同时有需要的属性, 上面的就是 app:imageUrl 和 app:error 同时需要;
且imageUrl是String, error是Drawable;
BindingAdapter里的属性可以不带 android, app,这些前缀;
Renamed Setters(重命名setter)
修改某个属性对应的setter名字, 使这个属性名更好理解, 不常用;
1 2 3 4 5 |
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), }) |
也可用于增加属性(给 Button 增加 selected 属性)
1: 在 ttrs.xml 中添加 select 属性
1 2 3 4 5 |
<resources> <declare-styleable name="Button"> <attr name="a" format="boolean" /> </declare-styleable> </resources> |
2: 在布局中使用
1 2 3 |
<Button ... app:selected="true" /> |
3: java 文件中定义转换
1 2 3 4 5 |
@BindingAdapter({"selected"}) public static void setBtSelected(Button bt, boolean isSelected) { bt.setSelected(isSelected); } |
Custom Conversions(自定义转换)
使用@BindingConversion修饰自定义转换方法;
一般情况下会根据提供的类型和需要的类型进行自动转换;
1 2 3 4 |
<View android:background="@{isError ? @color/red : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/> |
这里的background需要Drawable类型, @{}返回的是int类型, 这个时候就发生了转换;
1 2 3 4 |
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); } |
默认值
default=
默认显示文本
1 2 3 |
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName, default=PLACEHOLDER}"/> |
遇到的报错
如果项目中有其它错误, 导致 build 失败, 所以框架生成的文件都将找不到
不同的数据绑定类中,建议使用不同的变量名
布局绑定的对象里如果有布尔类型的属性, 不要使用 is 开头的变量名
参考资料
- https://developer.android.com/topic/libraries/data-binding/index.html
- https://github.com/LyndonChin/MasteringAndroidDataBinding
- https://realm.io/news/data-binding-android-boyar-mount/
0 Comments