Copyright notice: This article is the blogger’s original article, welcome to reprint!

Reprint please indicate the source: blog.csdn.net/guiying712/… This article is from: [Zhang Huayang’s blog]


    • Data binding library
    • DataBinding rely on
      • Build environment
      • Android Studio supports data binding
    • Layout and binding expressions
      • The data object
      • Data binding
      • Expression language
        • Common operations
        • An unsupported operation
        • The Null coalescing operator
        • Attribute references
        • Avoid null pointer exceptions
        • A collection of
        • String text
        • Resources Resources
      • The event processing
        • Method references
        • Listener binding
      • Imports, variables, and includes
        • Imports
        • Variables
        • Includes

Data binding library

The Data Binding library is a support library that allows developers to bind data sources to layout UI components in their apps declaratively rather than programmatically.

Typically, we define the layout in an Activity and invoke it in Java code using UI framework methods. For example, findViewById() is called in the following code to find a TextView control and bind it to the userName property of the viewModel variable:

TextView textView = findViewById(R.id.sample_text);
textView.setText(viewModel.getUserName());Copy the code

Here’s an example of how to use the data binding library to assign text directly to the controls of a layout file, so that we don’t have to call explicitly in Java code. Note the use of @{} in assignment expressions:

<TextView
    android:text="@{viewmodel.userName}" />Copy the code

Using the data binding library allows you to remove much of the UI framework’s calling code from your Activity, making it simpler and easier to maintain, improving Android application performance, and helping prevent memory leaks and null-pointer exceptions.

Let’s learn how to use the data binding library in Android applications.

DataBinding rely on

The first step is to get our development environment to support the use of data binding libraries, including support for data binding code in Android Studio.

The data binding library provides flexibility and wide compatibility – because it is a support library, it can be run on Android 4.0 (API level 14) or higher.

It is recommended that you use the latest AndroidGradle plugin in your project. All versions above 1.5.0 support data binding.

Build environment


First add the dataBinding element to the build.gradle file under the Android project Module, as shown in the following example:

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

Note: If an application module relies on a library that uses data binding, configure data binding even if the application module does not use data binding directly.

Android Studio supports data binding


Android Studio supports many editing features for data binding code. For example, it supports the following capabilities of data-binding expressions:

  • Syntax is highlighted
  • Expression language syntax error flag
  • XML code completion
  • Resources, including navigation (such as navigating to declarations) and quick documentation

Warning: Arrays and generics, such as Observables, can display false positives.

The Preview pane in the Layout Editor shows the default values for data binding expressions, if provided. For example, to display the my_default value on the TextView control:

<TextView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.firstName, default=my_default}"/>Copy the code

If you only need to display default values during the design phase of a project, you can use tools Attributes instead of default expression values. See the Tools Attributes Reference for more information.

Layout and binding expressions

The expression language allows you to write expressions that are distributed by views handling events, and the data binding library automatically generates classes that bind views to data objects in the layout.

The data-bound layout file is slightly different in that it starts with a Layout tag, followed by a data element and a View root element, which is the layout written in the non-bound layout file. Here is a simple data binding layout for activity_main.xml:

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"  android:text="@{user.firstName}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}"/> </LinearLayout> </layout>Copy the code

The user variable in the data element can be used in a layout:

<variable name="user" type="com.example.User" />Copy the code

The expression in the layout uses the @{} syntax to write the attributes of the user variable to android:text. The text of the TextView is set to the firstName attribute of the user variable.

Note: Layout expressions should be short and simple as they can’t be unit tested and IDE support is limited. To simplify layout expressions, you can customize Binding Adapters (more on that later).

The data object


Suppose we now have a generic object that describes the User entity (note: this User class comes up a lot later) :

public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }}Copy the code

This type of object has immutable data, which is usually read once in an application and never changes again. You can also use objects that follow a set of conventions, such as using accessor methods (that is, getter methods), as shown below:

public class User { private final String firstName; private final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; }}Copy the code

From a data binding perspective, the above two classes are equivalent. The @{user.firstName} expression is used by the Android :text attribute to access the firstName field of the first class and the getFirstName() method of the second class, and it can also be used to parse the firstName() method, if it exists.

Data binding


Each data Binding layout file generates a Binding class. By default, this Binding class name is based on the layout file name, converting the layout file name to Pascal spelling and adding a Binding suffix to it (Pascal spelling: Capitalize all the words that describe what a variable does, and then connect them directly, with no hyphens between the words). The layout file above is named activity_main.xml, so the corresponding generated class is MainActivityBinding. This class contains all bindings of layout attributes (for example, the user variable) to layout views and knows how to assign values to binding expressions. It is recommended to create binding methods in an inflating layout, as shown below:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}Copy the code

When the APP runs, the Test user is displayed in the UI. Alternatively, you can use LayoutInflater to get views, as shown in the following example:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());Copy the code

If you are using data-binding items inside your Fragment, ListView, or RecyclerView adapter, you may prefer to use the inflate() method of the binding class or DataBindingUtil class, as shown in the code below:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);Copy the code

Expression language


Common operations

The following operators and keywords can be used in the binding expression language:

  • count+ - / * %
  • String splicing+
  • logic&& | |
  • position& | ^
  • One yuan+ -! ~
  • shift>> >>> <<
  • Relationship between== > < >= <=
  • instanceof
  • group(a)
  • Literals – characters, strings, numbers,null
  • The method call
  • Field access
  • Array access[]
  • The ternary operating? :

Such as:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'Copy the code

An unsupported operation

Expression syntax does not support the following operations:

  • this
  • super
  • new
  • Explicit generic calls<T>

The Null coalescing operator

Null merge operator (??) If the left-hand operand of the operator is not null, select the left-hand operand; otherwise, select the right-hand operand:

android:text="@{user.displayName ?? user.lastName}"Copy the code

This is functionally equivalent to:

android:text="@{user.displayName ! = null ? user.displayName : user.lastName}"Copy the code

Attribute references

Expressions can refer to attributes in a class by using the following format, which is the same for fields, getters, and ObservableField objects:

android:text="@{user.lastName}"Copy the code

Avoid null pointer exceptions

The generated data binding code automatically checks for null values and avoids null pointer exceptions. For example, in the expression @{user.name}, if user is empty, user.name will default to null. If you refer to user.age of type int, the data binding defaults to 0.

A collection of

For convenience, use the [] operator to access common collections such as arrays, lists, sparse Lists, and maps.

<data> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> <variable name="list" type="List&lt; String&gt;" /> <variable name="sparse" type="SparseArray&lt; String&gt;" /> <variable name="map" type="Map&lt; String, String&gt;" <variable name="index" type="int"/> <variable name="key" type="String"/> </data>... Android: text = "@ [index]} {list"... Android: text = "@ {sparse [index]}"... android:text="@{map[key]}"Copy the code

String text

You can enclose attribute values with single quotation marks so that you can use double quotation marks in expressions, as shown in the following example:

android:text='@{map["firstName"]}'Copy the code

You can also enclose attribute values with double quotes. To do so, the string text should be enclosed in trailing quotes:

android:text="@{map[`firstName`]}"Copy the code

Resources Resources

You can access resources in expressions using the following syntax:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"Copy the code

Format strings and complex numbers can be estimated by providing arguments:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"Copy the code

When a complex number requires more than one argument, all arguments should be passed:


  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"Copy the code

Some resources require explicit type evaluation, as shown in the following table:

Type Normal reference Expression reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

The event processing

Data binding can also write expressions that handle View distribution events (for example, the onClick() method). With a few exceptions, the event attribute name is determined by the name of the listener method. For example, view.onClickListener has a method onClick(), so the event attribute is Android :onClick.

To avoid collisions, handling certain special events for click events requires properties other than Android :onClick. You can use the following properties to avoid these types of collisions:

Class Listener setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

You can use the following mechanisms to handle events:

  • Method references: In an expression you can refer to methods that match the listener method signature. When the expression is evaluated as a method reference, the data binding wraps the method reference and owner object in a listener that is set up on the target View. If the expression evaluates to zeronull, data binding does not create listeners, but sets onenullListener instead.
  • Listener binding: Lambda expression will be evaluated when an event occurs. Data binding always creates a listener, set on the View, that evaluates the lambda expression when the event is distributed.

Method references

Events can be tied directly to processing methods, similar to assigning Android: onClick to methods in an activity. The main advantage over the View’s onClick property is that the expression is processed at compile time, so you will receive a compile-time error if the method does not exist or is incorrectly signed.

The main difference between method references and listener bindings is that the actual listener implementation is created at data binding time, not at event firing time. If you want evaluate expression when an event occurs, you should use the listener binding.

To assign an event to its handler, use a normal binding expression whose value is the name of the method to call. For example, consider the following sample data object:

public class MyHandlers { public void onClickFriend(View view) { ... }}Copy the code

This binding expression can assign a click listener to the onClickFriend() method, as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="handlers" type="com.example.MyHandlers"/> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:onClick="@{handlers::onClickFriend}"/> </LinearLayout> </layout>Copy the code

Note: The signature of the method in the expression must exactly match the signature of the method in the listener object.

Listener binding

A listener binding is a binding expression that runs when an event occurs. They are similar to method references, but listener bindings allow you to run arbitrary data binding expressions. Of course, this requires you to use Gradle 2.0 or higher Android Gradle plugin.

In a method reference, the method parameters must match the parameters of the event listener. But in a listener binding, your return value must match the listener’s expected return value (unless it is expected to be void). For example, the Presenter class in the following code has the onSaveClick() method:

public class Presenter {
    public void onSaveClick(Task task){}
}Copy the code

You can bind the click event to the onSaveClick() method as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="task" type="com.android.example.Task" /> <variable name="presenter" type="com.android.example.Presenter" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> </LinearLayout> </layout>Copy the code

When using the callback in expression, data binding will automatically create the necessary listeners and registered for the event, when the View triggers, data binding will assess the given expression, as well as regular binding expression, in evaluating the listener expression, you can still get a null and thread-safe data binding.

In the above example, we did not define the View parameters passed to the onClick(View) method. Listener bindings give listener parameters two options: you can ignore all of the method’s parameters or name them all, and if you prefer named parameters, you can use them in expressions. For example, the above expression can be written as follows:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"Copy the code

Or, if you want to use arguments in an expression, it could look like this:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}Copy the code
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"Copy the code

You can also use lambda expressions with multiple arguments:

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}Copy the code
<CheckBox 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />Copy the code

If the value returned by the event being listened to is not invalid, the expression must return a value of the same type. For example, if you want to listen for long-press events, your expression should return Boolean:

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}Copy the code
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"Copy the code

If the expression cannot be evaluated due to a NULL object, the data binding returns the default value for that type. For example, reference types correspond to NULL, int to 0, Boolean to false, and so on.

If you need to use assertions in expressions (e.g., ternary operators? :), you can use void.

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"Copy the code

Avoid overly complex listeners

Listener expressions can be very powerful in that they make our code very easy to read. On the other hand, if the Listener contains complex expressions that make our layout difficult to read and maintain, these expressions should be as simple as passing available data from the UI to callback methods. You should implement any business logic inside the callback method called from the Listener expression.

Imports, variables, and includes


The data binding library provides features such as Imports, Variables, and includes. Imports can easily reference classes to your layout file. Variables allows you to describe attributes that can be used to bind expressions. Includes allows you to reuse complex layouts throughout your application.

Imports

Imports let us easily reference classes in layout files and use zero or more import elements within data elements. The following code imports the View class into the layout file:

<data>
    <import type="android.view.View"/>
</data>Copy the code

We can refer to the imported View class in a binding expression. The following example shows how to refer to the VISIBLE and GONE constants of the View class:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>Copy the code

Rename type

When there is a class name conflict, you can define an individual name for one of the classes. The following example renames the View class in the com.example.real.estate package to Vista:

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>Copy the code

So we can use Vista to reference com. Example.. Real estate View, while the View can be used for reference in the layout file android. View. The View.

Import other types

Imported types can be used as type references in variables and expressions. The following example shows User and List as types of variables:

<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List&lt; User&gt;" /> </data>Copy the code

Warning: Android Studio does not yet handle imports, so the auto-complete feature for imported variables may not be available in the IDE. Your application is always compiling, and you can solve IDE problems by using fully qualified names in variable definitions.

You can also convert partial expressions using imported types. The following example converts the Connection property to type User:

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>Copy the code

You can also use imported types when referencing static fields and methods in expressions. The following code imports the MyStringUtils class and references its capitalize method:

The < data > < import type = "com. Example. MyStringUtils" / > < variable name = "user" type = "com. Example. The user" / > < / data >... <TextView android:text="@{MyStringUtils.capitalize(user.lastName)}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>Copy the code

Variables

We can use multiple variable elements within the data element, each of which describes properties that can be set on the layout for binding expressions in the layout file. The following example declares the user, image, and note variables:

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note" type="String"/>
</data>Copy the code

Variable types are checked at compile time, so if a variable implements an Observable or an Observable collection, it should be reflected in the type. A variable is not an Observable if it is of a primitive type (int, float, etc.) or does not implement the Observable interface.

When various configurations (for example, landscape or vertical) have different layout files, variables are merged so that there are no conflicting variable definitions between these layout files.

The generated binding class has a setter and getter for each described variable, which will have a default value (reference type null, int 0, Boolean false, etc.) until the setter is called.

A special variable named context is generated as needed in the binding expression. The value of context is the context object returned by the getContext() method of the root View, and the context variable is overridden by an explicit variable declaration with that name.

Includes

In a layout that uses the app namespace, variables can be passed to the binding of the include layout. The following example shows passing the user variable to the name.xml and contact.xml layout files (if the user variable is declared in the name.xml and contact.xml layouts) :

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </LinearLayout> </layout>Copy the code

Data binding does not support include as a direct child of the merge. For example, the following layouts are not supported:

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.example.User"/> </data> <merge><! -- Doesn't work --> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </merge> </layout>Copy the code

This article was last updated on April 26, 2018.