Introduction to DataBinding

In traditional Android application development, the layout file is usually only responsible for the layout of the application interface. If you need to achieve page interaction, you need to call setContentView() to associate the Activity, fragment and XML layout file. The control is then found by its ID, and the control is then logically processed by code in the page. In this traditional approach, the page takes on the lion’s share of the workload and becomes bloated and difficult to maintain because of the amount of logical processing that needs to be done in activities and fragments, so Google introduced DataBiiding (view binding) to ease the workload.

The introduction of DataBinding allows layout files to take over some of the logic that normally belongs to Activity and Fragment pages, further reducing the coupling between Activity pages and XML layouts. DataBinding has the following features:

  • The code is more concise, readable, and allows UI control in XML files.
  • The findViewById operation is no longer required,
  • Can bind directly to Model entity classes for easy maintenance and extension.

In fact, DataBinding and THE MVVM architecture are inseparable, and DataBinding is exactly what Google implemented to better implement the MVVM architecture.

Two, DataBinding basic use

2.1 open viewBinding

ViewBinding can be enabled by module. To enable viewBinding in a module, add a viewBinding element to the build.gradle file, as shown below.

android {
        ...
        viewBinding {
            enabled = true}}Copy the code

DataBinding is a support package. After adding the script above, you will find four aar packages in the External Libraries of the project. Adapters, Commen, Runtime and ViewBinding.

When using DataBinding, if you want to ignore a layout file while generating a bound class, you can add the Tools :viewBindingIgnore=”true” attribute to the root view of the corresponding layout file, as shown below.

<LinearLayout
   ...
    tools:viewBindingIgnore="true">... </LinearLayout>Copy the code

2.2 Modifying the Layout File

The first step in using DataBinding is to transform the XML file. In fact, it is very easy to modify the layout file. Just change the outermost layer to

label on the basis of the original file content, as shown below.

<? 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>
Copy the code

Of course, we can also select the root layout, hold down Alt + Enter and select Convert to DataBinding Layout to generate the layout rules required by DataBinding. After attaching the Layout tag to the outermost layer of the layout and recompiling the project, the DataBinding library generates the corresponding Binding class that binds the XML layout file to the Model class, as shown below.

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(a); } @Override
    public void invalidateAll(a) {
        synchronized(this) {
                mDirtyFlags = 0x1L;
        }
        requestRebind(a); } @Override
    public boolean hasPendingBindings(a) {
        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(a) {
        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
}
Copy the code

The generated ActivityMainBindingImpl code is located in the app/ Build directory. The name of the generated Binding class is very special and corresponds to the name of the XML layout file. The specific connection is that the XML layout file is used, the underline is removed, and all single pieces are concatenated in the form of large humps, and a Binding is added at the end. For example, if my XML layout name is activity_main.xml, the generated Binding class name is ActivityMainBinding.

2.3 Binding Layout

To bind the XML layout file to the Activity without DataBinding, call the Activity’s setContentView() method, Or you can bind the layout by calling the inflate() method of LayoutInflate in the Fragment. If you use DataBinding, you need to use the DataBindingUtil class to bind the view, as shown below.

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); }}Copy the code

Use the setContentView() method of the DataBindingUtil class to bind the Activity, and the return value is the tool-generated Binding class. If it is Fragment, the code for the binding layout is as follows.

    private ActivityMainBinding binding;

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

    @Override
    public void onDestroyView(a) {
        super.onDestroyView(a); binding = null; }Copy the code

2.4 Adding a Data Label

After the previous steps, we have used DataBinding to bind the XML file to the UI component, using the Data tag and the variable tag if we want to accept Model data in the XML file.

Create the data tag under the LAYOUT tag of the XML file, and then create the variable tag in the data tag. The variable tag mainly uses the name attribute and type attribute, similar to the Java language variable declaration, which needs to specify the type and name of the variable. Create a new entity class named UserModel as follows.

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

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

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

    public String getLastName(a) {
        return this.lastName; }}Copy the code

Then declare the variable name to be used, the full path of the class, and other information in the data TAB of the layout, as shown below.

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

    ... // omit other code
</layout>
Copy the code

If User is used in more than one way, you can import it directly, so you don’t have to specify the entire package name path each time. Instead, classes in the java.lang.* package are automatically imported, so you can use it directly.

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

If the class names that are imported are the same, you can specify the alias using alias, as shown below.

    <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>
Copy the code

2.5 the use of variable

Once the variable property is declared in the XML file, you can then use it in XML. The layout expression @{} is used when using the variable property. You can get the value of the variable object passed in in the layout expression @{}, as shown below.

<? 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>
Copy the code

We then use the Binding class in the UI to pass data for each variable tag using the set method, as shown below.

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

After the above processing, we have set the UserModel object to the Binding class, so we can see the effect by running the code directly here.

2.6 Response Events

Previously, we covered some basic uses of DataBinding. You can assign values to certain properties of the control in a layout file so that the Model class data is bound directly to the layout, and the contents of the layout file can be refreshed immediately when the Model properties change. In addition to these simple usage scenarios, we can also use DataBinding to respond to user events.

Let’s modify the layout file, add a control to it, and add the following code to the Activity.

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

       }
  });
Copy the code

There’s another way we can do this. Layout expressions can not only pass in attributes of objects, but also call methods of objects. Start by creating a utility class that defines methods that respond to events, as shown below.

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

Then add the code for the click event to the layout file, as shown below.

<? 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">...// omit other code

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

    </LinearLayout>
</layout>
Copy the code

In the above code, you first declare an object for the ButtonClickListener class in the data tag and pass in the layout expression in the Button’s onClick property.

2.7 the include tag

In Android application development, in order to reuse layout files, we often use include tags when writing layouts, so that the same structure and content of the layout file can be used in multiple places. But if a layout file uses DataBinding and also uses include tags, how do you use the data in the layout file brought in by the nclude tag?

At this point, we need to introduce the layout variable User by naming the control XMLNS :app in the include tag of the same level page to pass the data object to the level 2 page, as shown below.

<? 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}" />
        
        ... // omit other code
    </LinearLayout>
</layout>
Copy the code

In the layout expression, the page variable user and include tag attribute values can be named arbitrarily. However, it should be noted that the name attribute in the variable tag of the second-level page must be the same as the name of the include tag attribute in the first-level page. As shown in the code for 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>
Copy the code

Third, BindingAdapter

3.1 BindingAdapter profile

When using the DataBinding library, DataBinding generates the corresponding XXXBindingAdapter class for control properties, such as TextViewBindingAdapter, It generates a method for each property of the TextView that can use DataBinding, and each method uses the @BindingAdapter annotation, whose parameters are the properties of the 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(a);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(a); } @BindingAdapter({"android:autoText"})
    public static void setAutoText(TextView view, boolean autoText) {
        KeyListener listener = view.getKeyListener(a); TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE;intinputType = 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)); }}Copy the code

In the BindingAdapter class, all methods are static, and each method uses the @BindingAdapter annotation, which declares the View property being acted on. When the DataBinding layout file is rendered, The static method corresponding to the property is automatically called.

3.2 Customizing the BindingAdapter

In addition to using the BindingAdapter class automatically generated by the library, developers can also customize the BindingAdapter class for developers to implement binding of properties not provided by the system, or to modify existing properties.

For example, there is a requirement that we want to be able to dynamically display images based on their address. How to do this if you use BindingAdapter?

Here, we use Glide image library to load the image, and you need to access the Internet to load the image, so please make sure you have Internet permission.

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

Next, we’ll write a custom BindingAdapter class that handles images. Then define a static method that is used primarily to add a BindingAdapter annotation with the value of the ImageView control’s custom property name, as shown below.

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)// Load the image to display
                   .error(R.drawable.ic_launcher_foreground)// The image displayed after the error
                   .into(view); }}}Copy the code

The loadImage() static method takes two arguments. The first argument must be the type of View being operated on, and the second argument must be the address of the image. When the URL property value of the ImageView control changes, dataBinding dynamically changes the ImageView properties by passing the ImageView instance and the new URL value to the loadImage() method.

We then associate the variable values in the XML file, as shown below.

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

Copy the code

Note that the outermost layer of the layout file contains the following named controls so that static methods defined by the @BindingAdapter tag can be invoked.

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

It should be noted that the code for the Activity and XML binding must use DataBindingUtil, as shown below.

DataBindingUtil.setContentView(this,R.layout.activity_main);
Copy the code

After the above processing, we can easily use the imageUrl attribute to load web images without worrying about thread switching, which is automatically done by the DataBinding library. Run the code above and it will look like this.Sometimes, we need to customize multiple attributes. How do we handle that? As with a parameter, we simply add the parameter using the BindingAdapter, as shown below.

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(a); options.placeholder(placeholder);
           options.error(error);
           Glide.with(view.getContext()).load(url)
                   .apply(options)
                   .into(view); }}}Copy the code

Then pass in the property values in the layout, as shown below.

<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}"/>
Copy the code

3.3 BindingConversion

In some cases, we need to convert the type when setting the property. This can be done with the @bindingConversion annotation. For example, the Android :background attribute receives a Drawable, but we need to set a color value to the databinding expression, which requires @bindingConversion.

First, create a ColorConversion class that converts color values to Drawable, as shown below.

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

Then, create a layout file and add the following code.

<? 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>
Copy the code

Use the same type when using the @bindingConversion annotation in the layout, otherwise an error will be reported.

4. Bidirectional binding

DataBinding itself is an observer implementation of View layer state. By binding views to ViewModel layer objects (such as LiveData), the View layer automatically updates the UI when the ViewModel layer data changes. This scenario is called one-way binding.

In practice, one-way binding is not sufficient for all requirements. For example, if the layout has an EditText, we want the corresponding Model class to be updated in real time as the user enters content in the input field, which requires bidirectional binding. DataBinding also supports this capability.Bidirectional binding requires the ObservableField class, which wraps an ordinary data object into an observable data object, which can be a primitive variable, a collection, or a custom type. To implement bidirectional binding, we need to define a ViewModel class that inherits from a BaseObservable and provides get and set methods, as shown below.

public class UserViewModel extends BaseObservable {

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

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

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name); }...// omit other code
}
Copy the code

We then use DataBinding in the XML layout file, as shown below.

<? 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="Save" />

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

    </LinearLayout>
</layout>
Copy the code

Note that, unlike the one-way binding, where the layout expression was @{}, the bidirectional binding uses the layout expression @={} with an extra equals sign.

Next, we add logic to the Activity to get user input, as shown below.

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(a); UserViewModel viewModel=new UserViewModel(a); viewModel.setName(name);
                activityMainBinding.setUserModel(viewModel); }}); }}Copy the code

After the above processing, bidirectional binding is almost complete. It can be found that bidirectional binding is very similar to LiveData in that it encapsulates ordinary data objects as observables. Theoretically, the two can be replaced by each other. However, LiveData has life cycle awareness and needs to call the observe() method to monitor. ObservableField is recommended for bidirectional binding, which does not require the observe() method and is relatively easy to maintain.

Five, use DataBinding in RecyclerView

5.1 Basic Usage

List layout is a very common scenario in Android application development. To achieve list layout, RecyclerView control is required. DataBinding supports DataBinding in RecyclerViieew.

With RcyclerView, you use Adapter, which instantiates the Item layout and then binds the data from the List to the layout, while DataBinding helps you instantiate the layout and bind the data.

First, we write the Adapter’s item layout, where we bind the User data using DataBinding. The code for item_user.xml is shown below.

<? 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>
Copy the code

Next, write the Adapter class business processing, the UserAdapter code shown below.

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(a) {
            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(a) {
        if (mDataList.size(a) >0) {return  mDataList.size(a); }return 0; }}Copy the code

As you can see, we used findViewById in the ViewHolder to instantiate the child control. Since we used DataBinding, we no longer need to do this. We simply pass in the generated Binding class. Then call the getRoot() method in super to return the root View.

Using DataBinding in RecyclerView is so simple that when the item data in the List changes, the contents of the List are updated accordingly.

Then, according to the basic use of RecyclerView, we add some test data in MainActivity and bind it to UserAdapter, the code is as follows.


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(a);initRecyclerView(a); }private void initData(a) {
        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(a) {
        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); }}Copy the code

5.2 Customizing a BindingAdapter

In the code above, setting LayoutManager and Adapter to RecyclerView is one of the more complex operations on the View, which can be simplified by customizing the BindingAdapter. First, define a new property that binds the data List directly to the layout file via DataBinding, and encapsulate the operations into a BindindAdapter. You no longer need to set up LayoutManager and Adapter operations in the Activity.

First, define the BindingAdapter, as shown below.

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); }}Copy the code

In the code above, we declare a new property users, then decorate the static method with @bindingAdapter, and then set the LayoutManager and Adapter to RecyclerView within the method. Next, we just need to use DataBinding in the 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">

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

    </data>

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

</layout>
Copy the code

Reference:

Android Navigation Android Jetpack WorkManager (7) Android Navigation Android Jetpack WorkManager (6) Android Navigation Android Jetpack Lifecycle Android LiveData Android Jetpack (3) ViewModel Android Jetpack (2) Lifecycle Android Jetpack architecture component (I) with AndroidX