关于java:Android-Jetpack架构组件八之DataBinding

30次阅读

共计 22160 个字符,预计需要花费 56 分钟才能阅读完成。

一、DataBinding 简介

在传统的 Android 利用开发中,布局文件通常只负责利用界面的布局工作,如果须要实现页面交互就须要调用 setContentView()将 Activity、fragment 和 XML 布局文件关联起来。而后通过控件的 id 找到控件,接着在页面中通过代码对控件进行逻辑解决。在这种传统的开发方式中,页面承当了大部分的工作量,大量的逻辑解决须要在 Activity、Fragment 中进行解决,因而页面显得臃肿不堪,保护起来也很艰难,为了加重页面的工作量,Google 提出了 DataBiiding(视图绑定)。

DataBinding 的呈现让布局文件承当了本来属于 Activity、Fragment 页面的局部逻辑,使得 Activity 页面和 XML 布局之间的耦合度进一步升高。DataBinding 次要有以下特点:

  • 代码更加简介,可读性高,可能在 XML 文件中实现 UI 管制。
  • 不再须要 findViewById 操作,
  • 可能与 Model 实体类间接绑定,易于保护和扩大。

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

二、DataBinding 根本应用

2.1 开启 viewBinding

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

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

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

应用 DataBinding 时,如果心愿在生成绑定类时疏忽某个布局文件,能够将 tools:viewBindingIgnore="true" 属性增加到相应布局文件的根视图中,如下所示。

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

2.2 批改布局文件

应用 DataBinding 的第一步,就是先革新 XML 文件。其实革新布局文件也特地简略,只须要在原来文件内容的根底上,最外层改为 <layout> 标签即可,如下所示。

<?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>

三、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 注解时要应用雷同类型,否则会报错。

四、双向绑定

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()办法,保护起来绝对简略。

五、在 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

正文完
 0