Layout and binding expressions
With the help of the expression language, you can write expressions to handle the events assigned by the view. The data binding library automatically generates the classes needed to bind the views in the layout to your data objects.
The data binding layout file is slightly different, starting with the root tag layout, followed by the data element and the view root element. This view element is the root in the unbound layout file. The following code shows a sample layout file:
<?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>
The user variable in data describes the properties that can be used in this layout.
<variable name="user" type="com.example.User" />
Expressions in the layout are written to attribute properties using the "@ {}" syntax. Here, the TextView text is set to the firstName property of the user variable:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" />
Note: layout expressions should be kept concise because they cannot be unit tested and have limited IDE support. To simplify layout expressions, you can use custom binding adapters.
data object
Now let's assume that you have a plain old object to describe the User entity:
Kotlin
data class User(val firstName: String, val lastName: String)
Java
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
This type of object has data that never changes. It is common for applications to contain data that will not change after reading once. You can also use objects that follow a set of conventions, such as the use of accessor methods in Java, as shown in the following example:
Kotlin
// Not applicable in Kotlin. data class User(val firstName: String, val lastName: String)
Java
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; } }
From a data binding perspective, these two classes are equivalent. The expression @ {user.firstName} for the android:text attribute accesses the firstName field in the previous class and the getFirstName() method in the latter class. Or, if the method exists, it will also resolve to firstName().
Binding data
The system generates a Binding class for each layout file. By default, the class name is based on the name of the layout file. It is converted to Pascal case and the Binding suffix is added at the end. The above layout file name is activity_main.xml, so the generated corresponding class is ActivityMainBinding. This class contains all bindings from layout properties (for example, user variables) to layout views and knows how to specify values for Binding expressions. The recommended Binding creation method is to create when expanding the layout, as shown in the following example:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.user = User("Test", "User") }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Test", "User"); binding.setUser(user); }
When running, the application will display the Test user in the interface. Alternatively, you can use layoutinflator to get the view, as shown in the following example:
Kotlin
val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
Java
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
If you want to use data binding items in the Fragment, ListView, or RecyclerView adapter, you may prefer to use the inflate() method of the binding class or DataBindingUtil class, as shown in the following code example:
Kotlin
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) // or val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
Java
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); // or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Expression language
Common functions
The expression language is very similar to expressions in managed code. You can use the following operators and keywords in the expression language:
- Arithmetic operators + - / *%
- String concatenation operator+
- Logical operator & &||
- Binary operator & |^
- Unary operator + -~
- Shift operator > > > > ><<
- Comparison operator = = > < > = < = (please note that < < needs to be escaped as <)
- instanceof
- Grouping operator ()
- Literal operator - character, string, number, null
- Type conversion
- Method call
- Field access
- Array access []
- Ternary operator?:
Example:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
Missing operation
The following operations are missing from the expression syntax that you can use in managed code:
- this
- super
- new
- Explicit generic call
Null merge operator
If the left operand is not Null, the Null merge operator (?) Select the left operand. If the left operand is Null, select the right operand.
android:text="@{user.displayName ?? user.lastName}"
This is functionally equivalent to:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Attribute reference
Expressions can reference attributes in classes using the following format, which is the same for fields, getter s, and ObservableField objects:
android:text="@{user.lastName}"
Avoid Null pointer exceptions
The generated data binding code will automatically check for null values and avoid null pointer exceptions. For example, in the expression @ {user.name}, if user is null, it is user The default value assigned by name is null. If you reference user Age, where the type of age is int, the data binding uses the default value of 0.
View reference
Expressions can reference other views in a layout by ID using the following syntax:
android:text="@{exampleText.text}"
Note: the binding class converts the ID to hump case.
In the following example, the TextView view references the EditText view in the same layout:
<EditText android:id="@+id/example_text" android:layout_height="wrap_content" android:layout_width="match_parent"/> <TextView android:id="@+id/example_output" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{exampleText.text}"/>
aggregate
For convenience, you can use the [] operator to access common collections, such as arrays, lists, sparse lists, and mappings.
<data> <import type="android.util.SparseArray"/> <import type="java.util.Map"/> <import type="java.util.List"/> <variable name="list" type="List<String>"/> <variable name="sparse" type="SparseArray<String>"/> <variable name="map" type="Map<String, String>"/> <variable name="index" type="int"/> <variable name="key" type="String"/> </data> ... android:text="@{list[index]}" ... android:text="@{sparse[index]}" ... android:text="@{map[key]}"
Note: to make XML free of syntax errors, you must escape the < character. For example: do not write in the form of List, but must write in the form of List < string >.
You can also use object The key notation refers to a value in the mapping. For example, @ {map[key]} in the above example can be replaced by @ {map.key}.
string literal
You can enclose the attribute value in single quotation marks, so you can use double quotation marks in the expression, as shown in the following example:
android:text='@{map["firstName"]}'
You can also use double quotation marks to enclose property values. If you do so, you should also use the inverse single quotation mark ` to enclose the literal of the string:
android:text="@{map[`firstName`]}"
resources
Expressions can reference application resources using the following syntax:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
You can evaluate format strings and plurals by providing parameters:
android:text="@{@string/nameFormat(firstName, lastName)}" android:text="@{@plurals/banana(bananaCount)}"
You can pass attribute references and view references as resource parameters:
android:text="@{@string/example_resource(user.lastName, exampleText.text)}"
When a complex number has multiple parameters, you must pass all parameters:
Have an orange Have %d oranges android:text="@{@plurals/orange(orangeCount, orangeCount)}"
Some resources require explicit type evaluation, as shown in the following table:
|Type | general 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 |
event processing
With data binding, you can write expressions that are dispatched from the view to handle events (for example, the onClick() method). The event attribute name is determined by the name of the listener method, with some exceptions. For example, view Onclicklistener has an onClick() method, so the feature of this event is android:onClick.
There are some event processing scripts specifically for click events. These processing scripts need to use features other than android:onClick to avoid conflicts. You can use the following properties to avoid these types of conflicts:
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 reference: in an expression, you can refer to a method that conforms to the listener's method signature. When the expression evaluation result is a method reference, the data binding encapsulates the method reference and owner object into the listener and sets the listener on the target view. If the expression evaluates to null, the data binding does not create a listener, but sets a null listener.
- Listener binding: These are lambda expressions that are evaluated when an event occurs. Data binding always creates a listener to set up on the view. After the event is dispatched, the listener evaluates the lambda expression.
Method reference
Events can be directly bound to script processing methods, similar to specifying android:onClick for methods in an Activity. A major advantage over the View onClick feature is that the expression is processed at compile time, so if the method does not exist or its signature is incorrect, you will receive a compile time error.
The main difference between method references and listener bindings is that the actual listener implementation is created when data is bound, not when an event is triggered. If you want to evaluate an expression when an event occurs, you should use listener binding.
To assign an event to its processing script, use a regular binding expression with the name of the method to call as the value. For example, consider the following example of layout data objects:
Kotlin
class MyHandlers { fun onClickFriend(view: View) { ... } }
Java
public class MyHandlers { public void onClickFriend(View view) { ... } }
The binding expression can assign the click listener of the view to the onClickFriend() method, as follows:
<?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>
Note: the method signature in the expression must be exactly the same as the method signature in the listener object.
Listener binding
Listener bindings are binding expressions that run when an event occurs. They are similar to method references, but allow you to run arbitrary data binding expressions. This feature is applicable to Android Gradle plug-ins of gradle version 2.0 and later.
In a method reference, the parameters of the method must match the parameters of the event listener. In the listener binding, only your return value must match the expected return value of the listener (except that the expected return value is invalid). For example, refer to the following presenter class with onsavelick() method:
Kotlin
class Presenter { fun onSaveClick(task: Task){} }
Java
public class Presenter { public void onSaveClick(Task task){} }
You can then bind the click event to the onsavelick () method, as follows:
<?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>
When a callback is used in an expression, the data binding automatically creates and registers the necessary listeners for the event. When a view triggers an event, the data binding evaluates the given expression. As with regular binding expressions, when evaluating these listener expressions, you still get the Null value and thread safety of the data binding.
In the above example, we have not defined the view parameter passed to onClick(View). The listener binding provides two listener parameter options: you can ignore all parameters of the method or name all parameters. If you want to name parameters, you can use them in expressions. For example, the above expression can be written as follows:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
Or, if you want to use parameters in an expression, use the following form:
Kotlin
class Presenter { fun onSaveClick(view: View, task: Task){} }
Java
public class Presenter { public void onSaveClick(View view, Task task){} }
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
You can use multiple parameters in a lambda expression:
Kotlin
class Presenter { fun onCompletedChanged(task: Task, completed: Boolean){} }
Java
public class Presenter { public void onCompletedChanged(Task task, boolean completed){} }
Xml usage
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
If the return type of the event you listen to is not a value of void, your expression must also return a value of the same type. For example, if you want to listen for a long press event, the expression should return a Boolean value.
Kotlin
class Presenter { fun onLongClick(view: View, task: Task): Boolean { } }
Java
public class Presenter { public boolean onLongClick(View view, Task task) { } }
Xml usage
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
If the expression cannot be evaluated due to a null object, the data binding returns the default value of that type. For example, the reference type returns null, int returns 0, boolean returns false, and so on.
If you need to use expressions with predicates (for example, ternary operators), you can use void as a symbol.
xml
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Avoid using complex listeners
Listener expressions are very powerful and can make your code very easy to read. On the other hand, listeners that contain complex expressions can make your layout difficult to read and maintain. These expressions should be as simple as passing available data from the interface to the callback method. You should implement any business logic in the callback method called from the listener expression.
Import, variables, and include
The data binding library provides functions such as import, variable, and include. With the import function, you can easily reference classes in layout files. The variable function allows you to describe the properties that can be used in binding expressions. By including functionality, you can reuse complex layouts throughout your application.
Import
With the import function, you can easily reference classes in layout files, just as in managed code. You can use multiple import elements in the data element or not. The following code example imports the View class into the layout file:
Xml
<data> <import type="android.view.View"/> </data>
Importing the View class allows you to reference it through a binding expression. The following example shows how to reference the VISIBLE and GONE constants of the View class:
Xml
<TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
Type alias
When there is a conflict between class names, one of the classes can be renamed with an alias. The following example will be com example. real. Rename the View class in the estate package to Vista:
<import type="android.view.View"/> <import type="com.example.real.estate.View" alias="Vista"/>
You can use Vista to reference com. In the layout file example. real. estate. View, use view to reference Android view. View.
Import other classes
Imported types can be used as type references in variables and expressions. The following example shows User and List as variable types:
<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List<User>"/> </data>
Note: Android Studio does not handle import yet, so the automatic filling function of import variables may not be available in your IDE. Your application can still be compiled, and you can solve this IDE problem by using fully qualified names in variable definitions.
You can also use the imported type to type convert part of the expression. The following example casts the connection property to the type User:
<TextView android:text="@{((User)(user.connection)).lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Imported types can also be used when static fields and methods are referenced in expressions. The following code will import the MyStringUtils class and reference its capitalization method:
<data> <import type="com.example.MyStringUtils"/> <variable name="user" type="com.example.User"/> </data> ... <TextView android:text="@{MyStringUtils.capitalize(user.lastName)}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Just like in managed code, the system will automatically import Java lang.*.
variable
You can use multiple variable elements in a data element. Each variable element describes an attribute that can be set on the layout and will be used in the binding expression 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>
The variable type is checked at compile time, so if the variable implements Observable or Observable set, it should be reflected in the type. If the variable is a base class or interface that does not implement the Observable interface, the variable is "unobservable".
If different configurations (such as landscape or portrait) have different layout files, the variables are merged together. There must be no conflicting variable definitions between these layout files.
In the generated binding class, each described variable has a corresponding setter and getter. Before calling setter, these variables always adopt the default managed code value, such as null for reference type, 0 for int, false for boolean, and so on.
The system will generate a special variable named context as needed to bind expressions. The value of context is the context object in the getContext() method of the root view. The context variable is replaced by an explicit variable declaration with that name.
contain
By using the variable names in the application namespace and attributes, variables can be passed from the included layout to the binding of the included layout. The following example shows an example from name XML and contact The user variable contained in the XML layout file:
<?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>
Data binding does not support include as a direct child of a merge element. For example, the following layouts are not supported:
<?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>