Android Data Binding 介绍使用

介绍

Data Binding 类库(Android 2.1(API level 7+))是用于编写xml layout 布局,并且尽量减少粘合代码对你的应用逻辑和布局上的绑定。


构建

在应用 module 的 build.gradle 添加 dataBinding 支持,Android Studio 版本必须 1.3+。

android {
    ....
    dataBinding {
        enabled = true
    }
}

XML布局文件

布局文件必须以 <layout/> 标签作为根标签,子标签包括一个 <data/> 元素和一个 <view/> 元素。

<?xml version="1.0" encoding="utf-8"?>
<!--根标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--data-->
    <data>
        <import type="android.view.View" />
        <import type="android.text.TextUtils" />
        <variable
            name="item"
            type="cn.com.bluemoon.washmaster.video.Industry" />
    </data>
    <!--view-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        ...
    </LinearLayout>
</layout>
  • import

data元素内可以使用零个或多个 import 元素。 这些就像在 Java 中一样可以轻松地引用类到你的布局文件中。

<data>
    <import type="android.view.View" />
    <import type="android.text.TextUtils" />
</data>
<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:visibility="@{TextUtils.isEmpty(item.remark) ?  View.GONE : View.VISIBLE}" />

如果类名有冲突的话,则需起别名。

<import type="android.view.View"/>
<import type="cn.com.bluemoon.washmaster.video.View" alias="Bview"/>
  • variable

variable 在布局中设置的属性,以用于布局文件中的绑定表达式。

<data> 
    <import type="android.graphics.drawable.Drawable"/> 
    <variable name="item" type="cn.com.bluemoon.washmaster.video.Industry" />
    <variable name="image" type="Drawable"/> 
</data>
  • Observable 接口

变量类型在编译时被检查,所以如果一个变量实现了 Observable 或者一个 observable collection,那么它应该被反映在类型中。 如果变量是没有实 Observable 接口的基类或接口,那么它将不会被观察!

//BaseObservable是Observable的子类
public class Industry extends BaseObservable {
    private String remark;
    private boolean likeFlag; //点赞

    @Bindable
    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Bindable
    public boolean isLikeFlag() {
        return likeFlag;
    }

    public void setLikeFlag(boolean likeFlag) {
        this.likeFlag = likeFlag;
        notifyPropertyChanged(BR.likeFlag);
    }
    ... 
}

@Bindable 注解给getter赋值

notifyPropertyChangedObservable接口通过方便的PropertyChangeRegistry来实现用于储存和有效地通知监听器,比如点赞需要动态改变颜色状态

BR 类似R.class的文件,通过编译后生成,@Bindable 或 variable 声明变量后会生成对应的int值

public class BR {
    public static final int _all = 0;
    public static final int authorInfo = 1;
    public static final int comeFrom = 2;
    public static final int commentNum = 3;
    public static final int contentId = 4;
    public static final int displayPic = 5;
    public static final int item = 6;
    public static final int likeFlag = 7;
    public static final int likeNum = 8;
    public static final int position = 9;
    public static final int remark = 10;
    public static final int title = 11;
    public static final int type = 12;
    public static final int viewModel = 13;

    public BR() {
    }
}
  • Observable 字段 

如果只有少许变量,可以使用ObservableField,或者 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}
  • ObservableArrayMap
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
<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);
<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"/>
  • include

variable变量可以从包含的布局中传递到包含的布局的绑定中:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="item" type="cn.com.bluemoon.washmaster.video.Industry" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <include layout="@layout/home" bind:item="@{item}"/>
        <include layout="@layout/contact" bind:item="@{item}"/>
    </LinearLayout>
</layout>

home.xmlcontact.xml 布局文件中都必须有一个 item 变量。数据绑定不支持 include 作为 merge 元素的直接子元素。


绑定数据

  • Activity

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    MainActivity binding = DataBindingUtil.setContentView(this, R.layout.main_activity); 
    Industry item = new Industry();
    binding.setVariable(BR.item, item);
}

  • Fragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    ViewDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment, false);
    Industry item = new Industry();
    binding.setVariable(BR.item, item);    
    return binding.getRoot();
}
  • RecyclerView

    class VideoAdapter extends  RecyclerView.Adapter {
        private List<Industry> mList;
        public VideoAdapter(List<Industry> list) {
            this.mList = list;
        }

        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
            ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.video_list, viewGroup, false);
            return new BaseViewHolder(binding);
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
            ((BaseViewHolder) viewHolder).getBinding().setVariable(BR.item, mList.get(i));
            ((BaseViewHolder) viewHolder).getBinding().setVariable(BR.position, i);
        }

        @Override
        public int getItemCount() {
            return mList.size();
        }

        public class BaseViewHolder extends RecyclerView.ViewHolder {
            protected ViewDataBinding binding;

            public BaseViewHolder(ViewDataBinding binding) {
                super(binding.getRoot());
                this.binding = binding;
            }

            public ViewDataBinding getBinding() {
                return binding;
            }
        }
    }

Views With IDs

  • 自定义 Binding 类名称

默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线(_)以及(_)后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding封装包下。例如,所述layout文件video_list.xml将生成VideoListBinding

Binding类可通过调整data元素中的class属性来重命名或放置在不同的包中。例如:

<data class=".VideoLayout">
    ...
</data>
    
<data class="com.bluemoon.VideoLayout">
    ...
</data>
    
  • 带ID的Views

编译后生成的Binding类中每个有id的View都会生成public final 属性。Binding在View层次结构上做单一的传递,提取有id的Views。这种机制比起某些Views使用findViewById还要快。

<?xml version="1.0" encoding="utf-8"?> 
<layout> 
    <data class=".VideoLayout"/> 
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
        xmlns:tools="http://schemas.android.com/tools" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:background="#F0F4F6" 
        tools:context=".HomeFragment"> 
        <android.support.v7.widget.RecyclerView 
            android:id="@+id/recyclerView" 
            android:layout_width="match_parent" 
            android:layout_height="match_parent" /> 
    </LinearLayout> 
</layout>
public class VideoLayout extends android.databinding.ViewDataBinding  {
    @NonNull
    public final android.support.v7.widget.RecyclerView recyclerView;
    ...
}

用法例如:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        VideoLayout binding = DataBindingUtil.inflate(inflater, R.layout.fragment_video, container, false);
        mRecyclerView = binding.recyclerView; //代替findViewById
        return binding.getRoot();
    }

拓展

使用官方提供给的API来进行AdapterView的绑定需要写很多代码,使用起来不方便,但是由于Data Binding Library提供丰富的扩展功能,所以出现了很多第三方的库来扩展它,下面就来介绍一个比较好用的库binding-collection-adapter,Github地址:https://github.com/evant/binding-collection-adapter

  • ViewModel的写法

使用ObservableList,如果有数据变化它会自动更新UI。

public class ViewModel {
  public final ObservableList<String> items = new ObservableArrayList<>();
  public final ItemBinding<String> itemBinding = ItemBinding.of(BR.item, R.layout.item);
}

如果有多种样式的布局,那么就需要把ItemBinding换成OnItemBind,如下: 

public final OnItemBind<String> onItemBind = new OnItemBind<String>() {
  @Override
  public void onItemBind(ItemBinding itemBinding, int position, String item) {
    itemBinding.set(BR.item, position == 0 ? R.layout.item_header : R.layout.item);
  }
};
  •  layout文件
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
      <import type="com.example.R" />
      <import type="me.tatarka.bindingcollectionadapter2.LayoutManagers" />
      <variable name="viewModel" type="com.example.ViewModel"/>
    </data>

    <ListView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"/>

    <android.support.v7.widget.RecyclerView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layoutManager="@{LayoutManagers.linear()}"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"/>

    <android.support.v4.view.ViewPager
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"/>

    <Spinner
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"
      app:itemDropDownLayout="@{R.layout.item_dropdown}"/>
</layout>

 更多用法请参考作者Github:https://github.com/evant/binding-collection-adapter


事件处理

  • 无参数

public class EventHandlers {
   // EventHandlers 执行click事件
    public void click(){
    //do
    }
}
//xml:
<variable
    name="handler"
    type="com.xx.xxx.EventHandlers" />

<Button
     android:layout_width="match_parent"
     android:layout_height="48dp"
     android:onClick="@{handler::click}" />

// UI类:绑定handler,如绑定ViewModel那样
EventHandlers handler = new EventHandlers();
binding.setHandler(handler);

或者

//xml:
<Button
     android:layout_width="match_parent"
     android:layout_height="48dp"
     android:onClick="@{() -> viewModel.click()}"
/>

//ViewModel:
public void click(){

}
  • 带参数

//xml:
<variable
    name="viewModel"
    type="com.xx.xxx.ViewModel" />
<variable
    name="obj"
    type="com.xx.xxx.User" />

<Button
     android:layout_width="match_parent"
     android:layout_height="48dp"
     android:onClick="@{() -> viewModel.click(obj.id)}" />
  •  带view

//xml:
<variable
    name="viewModel"
    type="com.xx.xxx.ViewModel" />
<variable
    name="obj"
    type="com.xx.xxx.User" />

<Button
     android:layout_width="match_parent"
     android:layout_height="48dp"
     android:onClick="@{(view) -> viewModel.click(obj.id, view)}" />

如果您需要使用带谓词的表达式(例如三元),则可以使用 void 作为符号。 

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
  • 返回值

如果正在侦听的事件返回值不是 void,则表达式必须返回相同类型的值。

public class Presenter {
    public boolean like(View cb, boolean isChecked){
       if (isChecked) {
           //do something 取消点赞成功
           return false;
       } else {
           //do something 点赞成功
           return true;
       }
    }
}
<CheckBox 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:onCheckedChanged="@{(cb, isChecked) -> presenter.like(cb, isChecked)}" />

表达式语言

常用表达式跟Java表达式很像,以下这些是一样的:

  • 数学 + - / * %

  • 字符串连接 +

  • 逻辑 && ||

  • 二进制 & | ^

  • 一元运算 + - ! ~

  • 移位 >> >>> <<

  • 比较 == > < >= <=

  • instanceof

  • 分组 ()

  • null

  • Cast

  • 方法调用

  • 数据访问 []

  • 三元运算 ?:

Null合并操作

  • ?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:
android:text="@{user.displayName ?? user.lastName}"
//等同于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

避免 NullPointerException

Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}中,如果user是null,user.name会赋予它的默认值(null)。如果你引用user.age,age是int类型,那么它的默认值是0

字符串

android:text='@{map["firstName"]}'
android:text="@{map[`firstName`]}"

资源

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{large? @string/largeText : @string/smallText}"

格式化

<string name="vinctor">vinctor is so bad %1$s!</string>
<TextView            
    android:layout_width="match_parent"            
    android:layout_height="wrap_content"            
    android:text="@{@string/vinctor(`boy`)}" />

表达式链 

  • 重复的表达式
<ImageView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<TextView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<CheckBox android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
//可以简化为:
<ImageView android:id=“@+id/avatar” android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<TextView android:visibility=“@{avatar.visibility}”/>
<CheckBox android:visibility="@{avatar.visibility}"/>
  • 隐式更新
<CheckBox android:id=”@+id/seeAds“/>
<ImageView android:visibility=“@{seeAds.checked ? View.VISIBLE : View.GONE}”/>

 自定义setter

有些xml属性需要自定义它对应的方法。比如android:margin  、android:paddingLeft、android:src等属性并没有相关setter。

BindingAdapter 注解的静态绑定适配器方法,可以为一个xml属性自定义setter方法。

  • paddingLeft

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}
  • src

@BindingAdapter({"headImage", "comeFrom"})
public static void headImage(ImageView view, String url, String comeFrom) {
        GlideApp.with(view.getContext())
                .load(url)
                .error(loadCircleImage(view, "2".equals(comeFrom)?
                R.mipmap.bluemoonlogo:R.mipmap.laundrymaster_avantar_guest))
                .circleCrop().into(view);
}
<ImageView
    android:layout_width="28dp"
    android:layout_height="28dp"
    android:scaleType="fitXY"
    app:headImage="@{item.authorInfo.headPicture}"
    app:comeFrom="@{item.comeFrom}"/>

双向绑定

双向绑定就是在UI 发生变化,同步更新data中的变量。

使用@= 来进行双向绑定 

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={model.name}"/>

 自定义双向绑定

(待更新。。。)

发布了18 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/LIANGJIANGLI/article/details/82977218