Android Jetpack架构组件(八)之DataBinding

1、DataBinding简介

在传统的Android应用开发中,布局文件一般只负责应用界面的布局工做,若是须要实现页面交互就须要调用setContentView()将Activity、fragment和XML布局文件关联起来。而后经过控件的id找到控件,接着在页面中经过代码对控件进行逻辑处理。在这种传统的开发方式中,页面承担了大部分的工做量,大量的逻辑处理须要在Activity、Fragment中进行处理,所以页面显得臃肿不堪,维护起来也很困难,为了减轻页面的工做量,Google提出了DataBiiding(视图绑定)。java

DataBinding的出现让布局文件承担了本来属于Activity、Fragment页面的部分逻辑,使得Activity页面和XML布局之间的耦合度进一步下降。DataBinding主要有如下特色:android

  • 代码更加简介,可读性高,可以在XML文件中实现UI控制。
  • 再也不须要findViewById操做,
  • 可以与Model实体类直接绑定,易于维护和扩展。

事实上,DataBinding和MVVM架构是分不开的,DataBinding正是Google为了可以更好的实现MVVM架构而实现的。api

2、DataBinding基本使用

2.1 开启viewBinding

视图绑定功能可按模块启用,要在某个模块中启用视图绑定,请将 viewBinding 元素添加到build.gradle 文件中,以下所示。网络

android {
        ...
        viewBinding {
            enabled = true
        }
    }

DataBinding 是一个 support 包,添加完后上面的脚本后会发现工程的的External Libraries中多了四个aar包。分别是adapters、commen、runtime和viewbinding。架构

使用DataBinding时,若是但愿在生成绑定类时忽略某个布局文件,能够将 tools:viewBindingIgnore="true" 属性添加到相应布局文件的根视图中,以下所示。app

<LinearLayout
   ...
    tools:viewBindingIgnore="true" >
   ...
</LinearLayout>

2.2 修改布局文件

使用DataBinding的第一步,就是先改造XML文件。其实改造布局文件也特别简单,只须要在原来文件内容的基础上,最外层改成<layout>标签便可,以下所示。ide

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
       ... 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" 
            android:textSize="24dp"
            android:text="HelloWord" />

       ... 
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

固然,咱们也能够选中根布局,按住【Alt + 回车键】而后选择 【Convert to data binding layout】也能够生成 DataBinding 须要的布局规则。在布局最外层加layout标签后,从新编译项目,DataBinding库就会生成对应的Binding类,该类用来实现XML布局文件与Model类的绑定,代码以下。工具

public class ActivityMainBindingImpl extends ActivityMainBinding  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = null;
    }
    // views
    @NonNull
    private final androidx.constraintlayout.widget.ConstraintLayout mboundView0;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 1, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            );
        this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];
        this.mboundView0.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x1L;
        }
        requestRebind();
    }

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
            return variableSet;
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
        }
        return false;
    }

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        // batch finished
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): null
    flag mapping end*/
    //end
}

生成的ActivityMainBindingImpl代码位于app/build目录下。生成Binding类的名字很特殊,它与XML布局文件的名字有对应关系,具体的联系就是,以XML布局文件为准,去掉下划线,全部单次以大驼峰的形式按顺序拼接,最后再加上Binding。好比,个人XML布局文件名是activity_main.xml,生成的Binding类名就是ActivityMainBinding。布局

2.3 绑定布局

没有使用DataBinding的时候,为了将XML布局文件与Activity进行绑定,须要调用Activity的setContentView()方法,或者是在Fragment中调用LayoutInflate的inflate()方法来进行布局的绑定。若是使用了DataBinding以后,就须要使用DataBindingUtil类来进行视图的绑定,以下所示。测试

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding; 
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding= DataBindingUtil.setContentView(this,R.layout.activity_main);     
    }
}

使用DataBindingUtil类的setContentView()方法对Activity进行绑定,其返回值就是工具生成的Binding类。若是是Fragment,则对应的绑定布局的代码以下。

private ActivityMainBinding binding;

    @Override
    public View onCreateView (LayoutInflater inflater,
                              ViewGroup container,
                              Bundle savedInstanceState) {
        binding = ActivityMainBinding.inflate(inflater, container, false);
        View view = binding.getRoot();
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

2.4 添加data标签

通过前面的步骤后,咱们已经使用DataBinding将XML文件与UI组件绑定起来,若是要在XML文件中接受Model数据,就须要用到data标签与variable标签。

在XML文件的layout标签下,建立data标签,在data标签中再建立variable标签,variable标签主要用到的就是name属性和type属性,相似于Java语言声明变量时,须要为该变量指定类型和名称。新建一个名为UserModel的实体类,代码以下。

public class UserModel {
    private String firstName;
    private String lastName;

    public UserModel(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return this.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }
}

而后在布局的 data 标签里声明要使用到的变量名、类的全路径等信息,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
      <variable
          name="user"
          type="com.xzh.jetpack.UserModel" />
    </data>

    ... //省略其余代码
</layout>

若是 User 有多处用到,也能够直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,因此能够直接使用。

<data>
   <import type="com.xzh.jetpack.UserModel" />
   <variable
       name="user"
       type="UserModel" />
  </data>

若是存在 import 的类名相同的状况,可使用 alias 指定别名,以下所示。

<data>
        <import type="com.xzh.jetpack.UserModel" />
        <import
            alias="TempUser"
            type="com.xzh.jetpack.uer.UserModel" />
        <variable
            name="userInfo"
            type="User" />
        <variable
            name="tempUserInfo"
            type="TempUser" />
    </data>

2.5 使用variable

在XML文件中声明好variable属性后,接下来就能够在XML使用它了。使用variable属性时须要使用到布局表达式: @{ }。能够在布局表达式@{ }中获取传入variable对象的值,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="com.xzh.jetpack.UserModel" />
        <variable
            name="user"
            type="UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}"
            android:hint="Tv1"
            android:textSize="24dp" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"
            android:textSize="24dp" />

    </LinearLayout>
</layout>

而后,咱们在UI界面中使用Binding类为每个variable标签使用set方法传递数据,以下所示。

binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
 UserModel user =new UserModel("zhang", "beijing");
 binding.setUser(user);

通过上面的处理后,咱们已经给UserModel对象设置给了Binding类,因此这里直接运行代码就能够看到效果了。

2.6 响应事件

前面咱们介绍了DataBinding的一些基本用法,咱们能够在布局文件中对控件某些属性进行赋值,使得Model类数据直接绑定在布局中,并且Model属性发生变化时,布局文件中的内容能够即时刷新。除了这些简单的使用场景外,咱们还可使用DataBinding响应用户事件。

咱们对布局文件作一下修改,在里面添加一个控件,而后在Activity中添加以下代码。

binding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
     public void onClick(View v) {

       }
  });

除此以外,咱们还可使用另一种方式。布局表达式不只能够传入对象的属性,也能够调用对象的方法。首先建立一个工具类,在类中定义响应事件的方法,以下所示。

public class ButtonClickListener {
    public void onClick(View view) {
        Log.d("ButtonClickListener","onClick...");
    }
}

而后在布局文件中添加点击事件的代码,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="btnHandler"
            type="com.xzh.jetpack.databinding.ButtonClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

       ...//省略其余代码

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮"
            android:textSize="24dp"
            android:onClick="@{btnHandler.onClick}"/>

    </LinearLayout>
</layout>

在上面的代码中,首先在data标签中为ButtonClickListener类声明对象,在Button的onClick属性中传入布局表达式便可。

2.7 include标签

在Android应用开发中,为了可以让布局文件获得复用,在编写布局的时候咱们常常会使用include标签,相同结构与内容的布局文件就能够在多处使用。可是若是一个布局文件中使用了DataBinding,同时也使用了include标签,那么如何使用nclude标签引入的布局文件中中的数据呢。

此时,咱们须要在同一级页面的include标签中,经过命名控件xmlns:app来引入布局变量User,将数据对象传递给二级页面,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="user"
            type="com.xzh.jetpack.UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <include
            layout="@layout/layout_data_binding"
            app:persondata="@{user}" />
        
        ... //省略其余代码
    </LinearLayout>
</layout>

布局表达式中直接传入页面变量user,include标签属性值能够任意取名,可是要注意的是,在二级页面的variable标签中的name属性,必须与一级页面中的include标签属性名一致,如layout_data_binding的代码所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="userData"
            type="com.xzh.jetpack.UserModel" />
    </data>

    <LinearLayout
        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="@{userData.firstName}"
            android:gravity="center" />

    </LinearLayout>
</layout>

3、BindingAdapter

3.1 BindingAdapter简介

使用DataBinding库时,DataBinding会针对控件属性生成对应的XXXBindingAdapter类,如TextViewBindingAdapter类,其对TextView的每一个可使用DataBinding的属性都生成了对应的方法,并且每一个方法都使用了@BindingAdapter注解,注解中的参数就是对应View的属性。

@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings({"WeakerAccess", "unused"})
@BindingMethods({
        @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
        @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
        @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
        @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
        @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
        @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})

public class TextViewBindingAdapter {

    private static final String TAG = "TextViewBindingAdapters";
    @SuppressWarnings("unused")
    public static final int INTEGER = 0x01;
    public static final int SIGNED = 0x03;
    public static final int DECIMAL = 0x05;

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        final CharSequence oldText = view.getText();
        if (text == oldText || (text == null && oldText.length() == 0)) {
            return;
        }
        if (text instanceof Spanned) {
            if (text.equals(oldText)) {
                return; // No change in the spans, so don't set anything.
            }
        } else if (!haveContentsChanged(text, oldText)) {
            return; // No content changes, so don't set anything.
        }
        view.setText(text);
    }

    @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
    public static String getTextString(TextView view) {
        return view.getText().toString();
    }

    @BindingAdapter({"android:autoText"})
    public static void setAutoText(TextView view, boolean autoText) {
        KeyListener listener = view.getKeyListener();

        TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE;

        int inputType = listener != null ? listener.getInputType() : 0;
        if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
            capitalize = TextKeyListener.Capitalize.CHARACTERS;
        } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
            capitalize = TextKeyListener.Capitalize.WORDS;
        } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
            capitalize = TextKeyListener.Capitalize.SENTENCES;
        }
        view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize));
    }
}

BindingAdapter类中,全部的方法都是static方法,而且每一个方法都使用了@BindingAdapter注解,注解中声明所操做的View属性,当使用了DataBinding的布局文件被渲染时,属性所对应的static方法就会自动调用。

3.2 自定义BindingAdapter

除了使用库自动生成的BindingAdapter类以外,开发者也能够自定义BindingAdapter类,供开发者来实现系统没有提供的属性绑定,或者是修改原有的属性。

例如,有下面这样一个需求:咱们但愿能够根据图片地址动态的改变显示图片。若是使用BindingAdapter 如何实现呢?

此处,咱们加载图片使用的是glide图片库,而且加载图片须要访问网路,因此请确保申请了网路权限。

<uses-permission android:name="android.permission.INTERNET"/>

接下来,咱们编写一个处理图片的自定义BindingAdapter类。而后定义一个静态方法,主要用于添加 BindingAdapter 注解,注解值是 ImageView 控件自定义的属性名,以下所示。

public class ImageBindingAdapter {

    @BindingAdapter({"url"})
    public static void loadImage(ImageView view, String url) {
       if(!TextUtils.isEmpty(url)){
           Glide.with(view)
                   .load(url)
                   .centerCrop()
                   .placeholder(R.drawable.ic_launcher_background)//加载中显示的图片
                   .error(R.drawable.ic_launcher_foreground)// 错误后显示的图片
                   .into(view);
       }
    }

}

能够发现没,loadImage()静态方法的两个参数,第一个参数必须是所操做的View类型,第二个参数是图片的地址。当 ImageView 控件的 url 属性值发生变化时,dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法,从而实现动态改变 ImageView的相关属性。

而后,咱们在 XML 文件中关联变量值,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center">
        <ImageView
            android:layout_width="300dp"
            android:layout_height="200dp"
            app:url="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"/>
    </LinearLayout>
</layout>

须要说明的是,请注意布局文件的最外层包含如下命名控件,这样才能调用@BindingAdapter标签订义的静态方法。

xmlns:app="http://schemas.android.com/apk/res-auto"

须要说明的是,Activity和XML绑定的代码必定要用DataBindingUtil,以下所示。

DataBindingUtil.setContentView(this,R.layout.activity_main);

通过上面的处理后,咱们就能够很方便的使用 imageUrl 属性来加载网络图片了,而且不用担忧线程切换问题,DataBinding 库会自动完成线程切换。运行上面的代码,效果以下所示。
在这里插入图片描述
有时候,咱们须要自定义多个属性,那如何处理呢?和一个参数同样,咱们只须要使用BindingAdapter添加参数便可,以下所示。

public class ImageBindingAdapter {

    @BindingAdapter(value = {"url", "placeholder", "error"})
    public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {
       if(!TextUtils.isEmpty(url)){
           RequestOptions options = new RequestOptions();
           options.placeholder(placeholder);
           options.error(error);
           Glide.with(view.getContext())
                   .load(url)
                   .apply(options)
                   .into(view);
       }
    }
}

而后在布局中传入属性值便可,以下所示。

<ImageView
       android:layout_width="300dp"
       android:layout_height="200dp"
       android:layout_marginTop="10dp"
       app:url="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"
       app:placeholder="@{@drawable/icon}"
       app:error="@{@drawable/error}"/>

3.3 BindingConversion

在某些状况下,咱们须要对设置属性时类型进行转化,类型转化能够借助注解 @BindingConversion 来完成。例如,android:background 属性接收的是一个 Drawable ,可是咱们在使用的时候须要给databinding 表达式中设置一个颜色值,此时就须要 @BindingConversion

首先,建立一个颜色转化的类ColorConversion,用于将颜色值转化为Drawable,以下所示。

public class ColorConversion {
    
    @BindingConversion
    public static ColorDrawable colorToDrawable(int color){
        return new ColorDrawable(color);
    }
}

而后,建立一个布局文件,添加以下代码。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data> </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        
        <!--类型转换-->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@{true ? @color/colorRed : @color/colorBlue}"/>
    </LinearLayout>
</layout>

在布局中使用 @BindingConversion 注解时要使用相同类型,不然会报错。

4、双向绑定

DataBinding的自己是对View层状态的一种观察者模式的实现,经过让View与ViewModel层可观察的对象(好比LiveData)进行绑定,当ViewModel层数据发生变化,View层也会自动进行UI的更新,此种场景称之为单向绑定。

但实际开发中,单向绑定并不能足够全部的需求。例若有下面的场景:若是布局中有一个EditText,当用户在输入框中输入内容时,咱们但愿对应的Model类可以实时更新,这就须要双向绑定,DataBinding一样支持这样的能力。
在这里插入图片描述
实现双向绑定须要用到ObservableField类,它可以将普通的数据对象包装成一个可观察的数据对象,数据能够是基本类型变量、集合,也能够是自定义类型。为了实现双向绑定,咱们须要先定义一个继承自BaseObservable的ViewModel类,并提供get和set方法,以下所示。

public class UserViewModel extends BaseObservable {

    private String name;
    private String address;
    private int age;

    @Bindable
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    ... //省略其余代码
}

而后,咱们在XML布局文件中使用DataBinding,以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="userModel"
            type="com.xzh.jetpack.UserViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <EditText
            android:id="@+id/et1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="@={userModel.name}" />

        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="30dp"
            android:paddingRight="30dp"
            android:textSize="24dp"
            android:text="保存" />

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24dp"
            android:text="@={userModel.name}" />

    </LinearLayout>
</layout>

须要注意的是,不一样于单向绑定,以前的布局表达式是@{}表达式,双向绑定使用的布局表达式是@={},多了一个等号。

接下来,咱们在Activity中添加获取用户输入的逻辑,以下所示。

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding= DataBindingUtil.setContentView(this,R.layout.activity_main);
        activityMainBinding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String name=activityMainBinding.et1.getText().toString();
                UserViewModel viewModel=new UserViewModel();
                viewModel.setName(name);
                activityMainBinding.setUserModel(viewModel);
            }
        });
    }
}

通过上面的处理后,双向绑定就基本完成了。能够发现,双向绑定与LiveData很是类似,都是将普通的数据对象封装成了可观察对象,理论上两者是能够互相替代的,但LiveData具备生命周期感知能力,而且须要调用observe()方法进行监听,而双向绑定中更推荐使用ObservableField,不须要使用observe()方法,维护起来相对简单。

5、在RecyclerView中使用DataBinding

5.1 基本使用

列表布局在Android应用开发中是很是常见的场景,实现列表布局须要使用RecyclerView控件,DataBinding支持在RecyclerViieew中实现数据绑定。

使用RcyclerView,就须要用到Adapter,在Adapter中实例化Item布局,而后将List中的数据绑定到布局中,而DataBinding就能够帮助开发者实例化布局并绑定数据。

首先,咱们编写Adapter的item布局,在item布局中使用DataBinding将User数据进行绑定,item_user.xml的代码以下所示。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.xzh.jetpack.UserModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.address}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.age}" />
    </LinearLayout>

</layout>

接下来,编写Adapter类业务处理,UserAdapter的代码以下所示。

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {

    private List<UserModel> mDataList;

    public UserAdapter(List<UserModel> mDataList) {
        this.mDataList = mDataList;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        ItemUserBinding binding;
        public ViewHolder(@NonNull ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding=(ItemUserBinding)binding;
        }

        public ItemUserBinding getBinding() {
            return binding;
        }
    }


    @NonNull
    @Override
    public UserAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemUserBinding binding= DataBindingUtil.inflate((LayoutInflater.from(parent.getContext())), R.layout.item_user,parent,false);
        return new ViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull UserAdapter.ViewHolder holder, int position) {
        UserModel model=mDataList.get(position);
        holder.getBinding().setUser(model);
    }

    @Override
    public int getItemCount() {
        if (mDataList.size()>0){
            return  mDataList.size();
        }
        return 0;
    }
}

能够发现,以前咱们都是须要在ViewHolder中进行findViewById对子控件进行实例化,因为咱们使用了DataBinding,因此再也不须要这些操做,只须要传入生成的Binding类便可,而后在super中调用getRoot()方法返回根View。

在RecyclerView中使用DataBinding就是如此简单,当List中的item数据发生变化时,列表中的内容也会随之更新。

而后,按照RecyclerView的基本使用方法,咱们在MainActivity添加一些测试数据,并将它和UserAdapter进行绑定,代码以下。

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private ActivityMainBinding activityMainBinding;
    private List<UserModel> userModels;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        initData();
        initRecyclerView();
    }

    private void initData() {
        userModels = new ArrayList<UserModel>();
        for (int i = 0; i < 10; i++) {
            UserModel userModel = new UserModel("zhangsan"+1, "beijing"+i, "age"+i);
            userModels.add(userModel);
        }
    }


    private void initRecyclerView() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        activityMainBinding.recycle.setLayoutManager(layoutManager);
        activityMainBinding.recycle.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
        UserAdapter adapter = new UserAdapter(userModels);
        activityMainBinding.recycle.setAdapter(adapter);
    }
}

5.2 自定义BindingAdapter

在上面的代码中,对RecyclerView设置LayoutManager和Adapter属于对View的一些复杂操做,这些操做能够经过自定义BindingAdapter的方式进行简化。首先,定义一个新的属性,将数据List直接经过DataBinding在布局文件中绑定,而且将这些操做都封装到BindindAdapter中,Activity中再也不须要设置LayoutManager和Adapter操做。

首先,定义BindingAdapter,以下所示。

public class UserBindingAdapter {

    @BindingAdapter("users")
    void setUsers(RecyclerView recyclerView, List<UserModel> users )  {
        LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
        UserAdapter adapter = new UserAdapter(users);
        recyclerView.setAdapter(adapter);
    }
}

在上面的代码中,咱们声明了一个新的属性users,而后使用@BindingAdapter修饰静态方法,而后在方法里面对RecyclerView设置LayoutManager和Adapter。接下来,咱们只须要布局中使用DataBinding便可。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="com.xzh.jetpack.UserModel" />
        <import type="java.util.List" />
        <variable
            name="users"
            type="List&lt;UserModel&gt;" />

    </data>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycle"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:users="@{users}"/>

</layout>

参考:
Android Jetpack架构组件(七)之WorkManager
Android Jetpack架构组件(六)之Room
Android Jetpack架构组件(五)之Navigation
Android Jetpack架构组件(四)之LiveData
Android Jetpack架构组件(三)之ViewModel
Android Jetpack架构组件(二)之Lifecycle
Android Jetpack架构组件(一)与AndroidX

相关文章
相关标签/搜索