前言
2015年的Google IO大会上,Android 团队发布了一个数据绑定框架(Data Binding Library),官方原生支持 MVVM 模型。以后可以直接在 layout 布局 xml 文件中绑定数据了,无需再 findViewById然后手工设置数据了。其语法和使用方式和 JSP 中的 EL 表达式非常类似。
配置:
- android {
- ....
- dataBinding {
- enabled = true
- }
- }
- ActivityLayoutDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_layout_detail);
替换掉setContentView即可,ActivityLayoutDetailBinding这个类是自动生成的和你的布局文件名字一样,如果你想要去改变名字的话
- <layout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:bind="http://schemas.android.com/apk/res-auto">
- <!--这里你也可以为Binding类进行命名,有三种形式
- 1、Custom:会在databinding包下
- 2、.Custom:会在当前的包名下创建
- 3、com.andly.Custom:会在指定的包名下进行创建-->
- <data class="Custom">
Part 1、先是声明类和对象
- <data>
- <!--java.lang.*包是自动导入的不需要你再次的导入-->
- <import type="android.view.View" />
- <!--当出现了import相同的类名的时候需要为其指定外号alias加以区分-->
- <import
- alias="CustomView"
- type="com.andly.administrator.andlydatabinding.myview.View" />
- <import type="com.andly.administrator.andlydatabinding.entry.User" />
- <import type="com.andly.administrator.andlydatabinding.event_handing.EventHandler" />
- <import type="java.util.List" />
- <import type="java.util.Map" />
- <import type="android.graphics.drawable.Drawable" />
- <variable
- name="image"
- type="Drawable" />
- <variable
- name="note"
- type="String" />
- <variable
- name="boo"
- type="boolean" />
- <variable
- name="view"
- type="View" />
- <!--这里不能用<>来表示泛型,要使用< >-->
- <variable
- name="list"
- type="List<User>" />
- <variable
- name="strList"
- type="List<String>" />
- <variable
- name="map"
- type="Map<String,String>" />
1、简易的引用
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@{note}"/>
- <!--使用Map这种Key Value的格式数据时
- 格式1:"@{map['key']}"
- 格式2: '@{map["key"]}'
- 格式3: "@{map[`key`]}" 反引号
- -->
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@{map[`one`]}" />
- List<User> users = new ArrayList<>();
- User u = new User();
- u.setName("List User Name Data");
- users.add(u);
- binding.setList(users);
- Map map = new HashMap();
- map.put("one", "Map One Data");
- binding.setMap(map);
- <!--数据绑定将自动检测null异常,如果你的表达式为null,它将会给它赋值为(null)
- 如果为int类型则默认为0-->
- <!--之前都是写三元运算符的形式,当然在数据绑定中也能够使用,但更推荐下面那种-->
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@{boo?note:null}" />
- <!--?? :null合并运算符,当左边为null会显示右边-->
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@{note??null}"
- android:textColor="#00FF00"
- android:textSize="18sp" />
- <!--引用资源文件-->
- <ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="@{boo?@dimen/large_padding:@dimen/small_padding}"
- android:src="@{image}" />
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text='@{String.valueOf(map[`one`])}'/>
然而上面引用的数值并不能随着用户的操作动态的改变,如果你想动态的改变需要使用Observable前缀的类,如:
- //使用绑定的集合
- /**
- * 如果要使用ObservableArrayMap类需要在layout里面导入 <import type="android.databinding.ObservableMap" />
- */
- user = new ObservableArrayMap<>();
- user.put(Fields.FIRST_NAME, "Google");
- user.put(Fields.LAST_NAME, "Inc.");
- user.put(Fields.AGE, 17);
- binding.setObserableMap(user);
- public class DataUser extends BaseObservable {
- //继承BaseObservable类,想对谁进行监听则需要在get方法上面添加@Bindable注解,在set方法里面使用notifyPropertyChanged
- //注意到的是在这个方法里面要传入BR.name参数,这是Binding类为该字段生成唯一变量进行绑定
- private String name;
- private int age;
- private String info;
- @Bindable
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- notifyPropertyChanged(BR.name);
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public String getInfo() {
- return info;
- }
- public void setInfo(String info) {
- this.info = info;
- }
- }
当你去执行小的工作的时候,想去节省时间或者减少配置可以使用ObservableField或者其兄弟
- //它们内部包含Observable对象,使用时要去创建public final类型的
- //为其赋值和取值操作:user.firstName.set("Google"); int age = user.age.get();
- public final ObservableField<String> name = new ObservableField<>();
- public final ObservableInt age = new ObservableInt();
- public final ObservableField<String> info = new ObservableField<>();
Part 3、添加事件处理方法
- <!--
- 绑定事件的格式有两种:
- 1、方法引用:直接用handle.Click或者handle::Click 推荐后者
- -->
- <Button
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@{user.name}"
- android:onClick="@{handle::Click}"/>
- <!--
- 2、监听绑定:使用()组,括号里面所填的是你为参数起的名字,这样你就可以在后面的括号进行引用
- 如果你监听的事件需要返回值,那么你的方法也要返回一个相同类型
- -->
- <Button
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:onClick="@{()->handle.eventHandler(user)}"
- android:text="传入布局文件中的数据" />
- <Button
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:onClick="@{(thisView)->handle.eventHandlerView(thisView,user)}"
- android:text="传入此View" />
- <!--如果你需要为一个点击事件设置一个断言,那么使用void作为一个标志,表示什么也不做-->
- <Button
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:onClick="@{(v)->handle.isVisible(v)?handle.doSomething():void}"
- android:text="判断是否为visible" />
- <!--对于一些控件有自己专门的单击事件,需要创建下面的属性进行避免
- SearchView android:onSearchClick
- ZoomControls android:onZoomIn
- ZoomControls android:onZoomOut-->
- <SearchView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:onClick="@{(v)->handle.searchClick(v)}"
- android:onSearchClick="@{(v)->handle.onSearchClick(v)}">
- </SearchView>
- public void checkChanged(View view, boolean isCheck) {
- System.out.println("checkChanged:" + view + " " + isCheck);
- }
- public boolean longClick(View view) {
- System.out.println("longClick:" + view);
- return true;
- }
(沉璧浮光补充,https://github.com/ConnorLin/DataBindingDemo)二者主要区别在于方法调用在编译时处理,而监听绑定于事件发生时处理。
Part 4、在布局中使用include
如果你需要用到从xml传过来的数据需要去使用bind:user属性,这里的user是你定义的实体类名
- <!--当你使用include的时候,你可以使用命名空间和属性中的变量名
- 来将数据传送到另一个布局中去,值得注意的是当include的父节点为merge时将不支持-->
- <include
- layout="@layout/detail_include"
- bind:user="@{user}" />
然后只需要在include布局里面声明之后便可以直接使用了。
Part 5、在布局中使用ViewStub
- /**
- * 为ViewStub设置监听,当显示的时候为它绑定数据,因为当不显示的ViewStub会在视图中消失
- */
- vs = (ViewStub) findViewById(R.id.viewstub);
- vs.setOnInflateListener(new ViewStub.OnInflateListener() {
- @Override
- public void onInflate(ViewStub stub, View inflated) {
- ViewstubBinding viewstubBinding = ViewstubBinding.bind(inflated);
- Info info = new Info();
- info.setInfo("Andly Info");
- viewstubBinding.setInfo(info);
- Drawable d = getResources().getDrawable(R.mipmap.ic_launcher);
- viewstubBinding.setDrawable(d);
- }
- });
- }
- public void toggleViewStub(View view) {
- vs.inflate();
- }
Part 6、在布局中使用RecycleView控件
1、添加RecycleView控件
- <!--
- 这里使用到了自定义属性,因为RecycleView里面有setAdapter方法,所以这里可以直接用app:adapter
- -->
- <android.support.v7.widget.RecyclerView
- android:id="@+id/rv"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:adapter="@{adapter}" />
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- ViewDataBinding viewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutId, parent, false);
- return new ViewHolder(viewDataBinding);
- }
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- holder.binding.setVariable(variable, list.get(position));
- holder.binding.executePendingBindings();
- //当然这里你也可以为其设置点击如:
- //holder.binding.getRoot.setOnclickListener()
- }
- @Override
- public int getItemCount() {
- return list.size();
- }
- class ViewHolder extends RecyclerView.ViewHolder {
- ViewDataBinding binding;
- public ViewHolder(ViewDataBinding binding) {
- super(binding.getRoot());
- this.binding = binding;
- }
- }
- //这里注意的是一定要是BR.dataInfo不能是其它的常数
- MyAdapter adapter = new MyAdapter(list, R.layout.rv_item, BR.dataInfo);
- binding.setAdapter(adapter);
- binding.rv.setLayoutManager(new LinearLayoutManager(this));
- <ImageView
- android:layout_width="150dp"
- android:layout_height="90dp"
- app:imageError="@{@drawable/android}"
- app:imagePath="@{dataInfo.imageUrl}" />
- //当你在一个方法只需要一个参数的时候可以使用@BindingAdapter("imageUrlStr"),加上之后就可以在布局文件中直接使用imageUrlStr
- //运行之后就会调用loadImage方法
- @BindingAdapter("imageUrlStr")
- public static void loadImage(ImageView iv, String url) {
- Glide.with(iv.getContext()).load(url).into(iv);//这里使用Glide库
- }
- //上面是为loadImage传入一个参数,当传入两个或多个参数的时候应使用@BindingAdapter({"imagePath", "imageError"})
- //这个的ImageView自定义了两个属性一个是imagePath传入的是url,一个是imageError为Drawable
- @BindingAdapter({"imagePath", "imageError"})
- public static void downloadImage(ImageView iv, String url, Drawable error) {
- Glide.with(iv.getContext()).load(url).error(error).into(iv);
- }
上面的方法使用的是静态方法,如果你不想使用静态方法你需要重写一个数据绑定组件类去实现DataBindingComponent
- public class MyComponent implements android.databinding.DataBindingComponent {
- private Utils utils;
- @Override
- public Utils getUtils() {
- if (utils == null) {
- utils = new Utils();
- }
- return utils;
- }
- }
- //第一种方式
- DataBindingUtil.setDefaultComponent(new MyComponent());
- //第二种方式
- ActivityMyListViewBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_my_list_view,new MyComponent());
- //第三种方式
- DataBindingUtil.bind(root,new MyComponent());
最后有个不起眼的小功能,就是当使用数据绑定的时候在预览界面不能看到显示的内容,这时你可以为你的控件设置默认显示内容
- android:text="@{placeName,default=PLACEHOLDER}"
注意:
不允许使用混合类型
- <!--值得注意的是
- android:background="@{boo?@color/red:@drawable/background}"
- 这么写将会发生错误,因为在BindingConversion默认实现为:
- @BindingConversion
- public static ColorDrawable convertColorToDrawable(int color) {
- return new ColorDrawable(color);
- }
- -->
- <ImageView
- android:layout_width="100dp"
- android:layout_height="100dp"
- android:layout_marginTop="20dp"
- android:background="@{boo?@color/red:@color/green}" />
源码下载:http://download.csdn.NET/detail/weiwozhiyi/9644657