909 lines
38 KiB
Plaintext
909 lines
38 KiB
Plaintext
page.title=Data Binding Guide
|
||
|
||
@jd:body
|
||
<p>Data Binding allows you write declarative layouts and minimize the glue code
|
||
that is necessary to bind your application logic and layouts.</p>
|
||
|
||
|
||
<h2 id=build_environment>Build Environment</h2>
|
||
|
||
|
||
<p><strong>Setting Up Work Environment:</strong></p>
|
||
|
||
<p>Data Binding EAP only supports gradle.</p>
|
||
|
||
<p>To set up your application, unzip the provided bundle to a location. It has 3
|
||
sections</p>
|
||
|
||
<ul>
|
||
<li> <em>maven-repo:</em> which keeps the data-binding libraries
|
||
<li> <em>samples:</em> Sample applications
|
||
<li> <em>databinding.properties:</em> Properties file that can be used to integrate with your app
|
||
</ul>
|
||
|
||
<p>Add the following section to the project’s build.gradle file (not the module's
|
||
build.gradle) and replace <code><BUNDLE_FOLDER> </code>with the absolute path of the bundle that you’ve unzipped in the previous step.</p>
|
||
|
||
<pre class=prettyprint>
|
||
buildscript {
|
||
<strong>def </strong>eapFolder = '<BUNDLE_FOLDER>'
|
||
<strong> def </strong>Properties props = <strong>new </strong>Properties()
|
||
props.load(<strong>new </strong>FileInputStream(<strong>"</strong>${eapFolder}<strong>/databinding.properties"</strong>))
|
||
props.mavenRepoDir = <strong>"</strong>${eapFolder}<strong>/</strong>${props.mavenRepoName}<strong>"
|
||
</strong>ext.config = props
|
||
repositories {
|
||
jcenter()
|
||
maven {
|
||
url config.mavenRepoDir
|
||
}
|
||
}
|
||
dependencies {
|
||
classpath <strong>"com.android.tools.build:gradle:1.1.3"
|
||
</strong>classpath <strong>"com.android.databinding:dataBinder:</strong>${config.snapshotVersion}<strong>"
|
||
<em></strong> </em>}
|
||
}
|
||
allprojects {
|
||
repositories {
|
||
jcenter()
|
||
maven {
|
||
url config.mavenRepoDir
|
||
}
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>Next, add the following lines to the <em>build.gradle</em>
|
||
file of each module that will use data-binding. The application module must
|
||
have this, even if only its libraries use data binding.</p>
|
||
|
||
<pre class=prettyprint>
|
||
apply plugin: <strong>'com.android.databinding'
|
||
</strong>dependencies {
|
||
compile <strong>"com.android.databinding:library:</strong>${config.snapshotVersion}<strong>"
|
||
</strong> compile <strong>"com.android.databinding:baseLibrary:</strong>${config.snapshotVersion}<strong>"
|
||
</strong> compile <strong>"com.android.databinding:adapters:</strong>${config.snapshotVersion}<strong>"
|
||
</strong> provided <strong>"com.android.databinding:annotationprocessor:</strong>${config.snapshotVersion}<strong>"
|
||
</strong>}
|
||
</pre>
|
||
|
||
|
||
<h2 id="data_binding_layout_files">Data Binding Layout Files</h2>
|
||
|
||
|
||
<h3 id="writing_expressions">Writing your first data binding expressions:</h3>
|
||
|
||
<p>Data-binding layout files are slightly different and start with a root tag of
|
||
<strong>layout</strong> followed by a <strong>data</strong> element and a
|
||
<strong>view</strong> root element. This view element is what your root would
|
||
be in a non-binding layout file.A sample file looks like this:</p>
|
||
|
||
<pre class=prettyprint>
|
||
<em><?<strong></em>xml version="1.0" encoding="utf-8"<em></strong>?>
|
||
</em><<strong>layout xmlns:android="http://schemas.android.com/apk/res/android"</strong>>
|
||
<<strong>data</strong>>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
</<strong>data</strong>>
|
||
<<strong>LinearLayout
|
||
android:orientation="vertical"
|
||
android:layout_width="match_parent"
|
||
android:layout_height="match_parent"</strong>>
|
||
<<strong>TextView android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
android:text="@{user.firstName}"</strong>/>
|
||
<<strong>TextView android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
android:text="@{user.lastName}"</strong>/>
|
||
</<strong>LinearLayout</strong>>
|
||
</<strong>layout</strong>>
|
||
</pre>
|
||
|
||
<p>The user <strong>variable</strong> within <strong>data</strong> describes a property that may be used within this layout.</p>
|
||
|
||
<pre class=prettyprint>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
</pre>
|
||
|
||
<p>Expressions within the layout are written in the attribute properties using the
|
||
“<code>@{}</code>” syntax. Here, the TextView’s text is set to the firstName property of user:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>TextView android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
android:text="@{user.firstName}"</strong>/>
|
||
</pre>
|
||
|
||
|
||
<h3 id="data_object">Data Object</h3>
|
||
|
||
<p>Let’s assume for now that you have a plain-old Java object (POJO) for User:</p>
|
||
<pre class=prettyprint>
|
||
<strong>public class </strong>User {
|
||
<strong>public final </strong>String <strong>firstName</strong>;
|
||
<strong>public final </strong>String <strong>lastName</strong>;
|
||
<strong>public </strong>User(String firstName, String lastName) {
|
||
<strong>this</strong>.<strong>firstName </strong>= firstName;
|
||
<strong>this</strong>.<strong>lastName </strong>= lastName;
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>This type of object has data that never changes. It is common in applications
|
||
to have data that is read once and never changes thereafter. It is also
|
||
possible to use a JavaBeans objects:</p>
|
||
<pre class=prettyprint>
|
||
<strong>public class </strong>User {
|
||
<strong>private final </strong>String <strong>firstName</strong>;
|
||
<strong>private final </strong>String <strong>lastName</strong>;
|
||
<strong>public </strong>User(String firstName, String lastName) {
|
||
<strong>this</strong>.<strong>firstName </strong>= firstName;
|
||
<strong>this</strong>.<strong>lastName </strong>= lastName;
|
||
}
|
||
<strong>public </strong>String getFirstName() {
|
||
<strong>return this</strong>.<strong>firstName</strong>;
|
||
}
|
||
<strong>public </strong>String getLastName() {
|
||
<strong>return this</strong>.<strong>lastName</strong>;
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>From the perspective of data binding, these two classes are equivalent. The
|
||
expression <strong><code>@{user.lastName}</code></strong> used for the TextView’s <strong><code>android:text</code></strong> attribute will access the <strong><code>firstName</code></strong> field in the former class and the <code>getFirstName()</code> method in the latter class.
|
||
</p><h3 id=binding_data>Binding Data</h3>
|
||
|
||
<p>By default, a Binding class will be generated based on the name of the layout
|
||
file, converting it to Pascal case and suffixing “Binding” to it. The above
|
||
layout file was <code>activity_main.xml</code> so the generate class was <code>ActivityMainBinding</code>. This class holds all the bindings from the layout properties (e.g. the <code>user</code> variable) to the layout’s Views and knows how to assign values for the binding
|
||
expressions.The easiest means for creating the bindings is to do it while inflating:
|
||
</p>
|
||
|
||
<pre class=prettyprint>
|
||
@Override
|
||
<strong>protected void </strong>onCreate(Bundle savedInstanceState) {
|
||
<strong>super</strong>.onCreate(savedInstanceState);
|
||
ActivityMainBinding binding = DataBindingUtil.<em>setContentView</em>(<strong>this</strong>, R.layout.<em><strong>main_activity</strong></em>);
|
||
User user = <strong>new </strong>User(<strong>"Test"</strong>, <strong>"User"</strong>);
|
||
binding.setUser(user);
|
||
}
|
||
</pre>
|
||
|
||
<p>You’re done! Run the application and you’ll see Test User in the UI.Alternatively, you can get the view via:
|
||
</p><pre class=prettyprint>
|
||
MainActivityBinding binding = MainActivityBinding.<em>inflate</em>(getLayoutInflater());
|
||
</pre>
|
||
|
||
<p>If you are using data binding items inside a ListView or RecyclerView adapter,
|
||
you may prefer to use:
|
||
</p><pre class=prettyprint>
|
||
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
|
||
false);
|
||
//or
|
||
ListItemBinding binding = DataBindingUtil.<em>inflate</em>(layoutInflater, R.layout.<em><strong>list_item</strong></em>, viewGroup, <strong>false</strong>);
|
||
</pre>
|
||
|
||
|
||
<h2 id=layout_details>Layout Details</h2>
|
||
|
||
|
||
<h3 id=imports>Imports</h3>
|
||
|
||
<p>Zero or more <strong><code>import</code></strong> elements may be used inside the <strong><code>data</code></strong> element. These allow easy reference to classes inside your layout file, just
|
||
like in Java.
|
||
</p><pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="android.view.View"</strong>/>
|
||
</<strong>data</strong>>
|
||
</pre>
|
||
|
||
<p>Now, View may be used within your binding expression:
|
||
</p><pre class=prettyprint>
|
||
<<strong>TextView
|
||
android:text="@{user.lastName}"
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"</strong>/>
|
||
</pre>
|
||
|
||
<p>When there are class name conflicts, one of the classes may be renamed to an
|
||
“alias:”</p>
|
||
<pre class=prettyprint>
|
||
<<strong>import type="android.view.View"</strong>/>
|
||
<<strong>import type="com.example.real.estate.View"
|
||
alias="Vista"</strong>/>
|
||
</pre>
|
||
|
||
<p>Now, <strong><code>Vista</code></strong> may be used to reference the <code>com.example.real.estate.View</code> and <strong><code>View</code></strong> may be used to reference <code>android.view.View </code>within the layout file.Imported types may be used as type references in variables and expressions:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="com.example.User"</strong>/>
|
||
<<strong>import type="java.util.List"</strong>/>
|
||
<<strong>variable name="user" type="User"</strong>/>
|
||
<<strong>variable name="userList" type="List<User>"</strong>/>
|
||
</<strong>data</strong>>
|
||
…
|
||
<<strong>TextView
|
||
android:text="@{((User)(user.connection)).lastName}"
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|
||
<p>Imported types may also be used when referencing static fields and methods in
|
||
expressions:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="com.example.MyStringUtils"</strong>/>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
</<strong>data</strong>>
|
||
…
|
||
<<strong>TextView
|
||
android:text="@{MyStringUtils.capitalize(user.lastName)}"
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|
||
<p>Just as in Java, <code>java.lang.*</code> is imported automatically.</p>
|
||
<h3 id=variables>Variables</h3>
|
||
|
||
<p>Any number of <strong><code>variable</code></strong> elements may be used inside the <strong><code>data</code></strong> element. Each <strong><code>variable</code></strong> element describes a property that may be set on the layout to be used in
|
||
binding expressions within the layout file.</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="android.graphics.drawable.Drawable"</strong>/>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
<<strong>variable name="image" type="Drawable"</strong>/>
|
||
<<strong>variable name="note" type="String"</strong>/>
|
||
</<strong>data</strong>>
|
||
</pre>
|
||
|
||
<p>The variable types are inspected at compile time, so if a variable implements <a href="#observable_objects">Observable</a>, <a href="#observable_collections">observable collection</a>, that should be reflected in the type. If the variable is a base class or
|
||
interface that does not implement the Observable* interface, the variables will <strong>not be</strong> observed!</p>
|
||
|
||
<p>When there are different layout files for various configurations (e.g.
|
||
landscape or portrait), the variables will be combined. There must not be
|
||
conflicting variable definitions between these layout files.</p>
|
||
|
||
<p>The generated binding class will have a setter and getter for each of the
|
||
described variables. The variables will take the default Java values until the
|
||
setter is called — <code>null</code> for reference types, <code>0</code> for <code>int</code>, <code>false</code> for <code>boolean</code>, etc.</p>
|
||
|
||
<h3 id=custom_binding_class_names>Custom Binding Class Names</h3>
|
||
|
||
<p>By default, a Binding class is generated based on the name of the layout file,
|
||
starting it with upper-case, removing underscores ( _ ) and capitalizing the
|
||
following letter and then suffixing “Binding”. This class will be placed in a
|
||
databinding package under the module package. For example, the layout file <code>contact_item.xml</code> will generate <code>ContactItemBinding</code>. If the module package is <code>com.example.my.app</code>, then it will be placed in <code>com.example.my.app.databinding</code>.</p>
|
||
|
||
<p>Binding classes may be renamed or placed in different packages by adjusting the <strong><code>class</code></strong> attribute of the <strong><code>data</code></strong> element. For example:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data class="ContactItem"</strong>>
|
||
...
|
||
</<strong>data</strong>>
|
||
</pre>
|
||
|
||
<p>This generates the binding class as <code>ContactItem</code> in the databinding package in the module package. If the class should be
|
||
generated in a different package within the module package, it may be prefixed
|
||
with “.”:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data class=".ContactItem"</strong>>
|
||
...
|
||
</<strong>data</strong>>
|
||
</pre>
|
||
|
||
In this case, <code>ContactItem</code> is generated in the module package directly.Any package may be used if the full package is provided:
|
||
<pre class=prettyprint>
|
||
<<strong>data class="com.example.ContactItem"</strong>>
|
||
...
|
||
</<strong>data</strong>>
|
||
</pre>
|
||
|
||
|
||
<h3 id=includes>Includes</h3>
|
||
|
||
<p>Variables may be passed into an included layout's binding from the containing
|
||
layout by using the application namespace and the variable name in an
|
||
attribute:</p>
|
||
<pre class=prettyprint>
|
||
<em><?<strong></em>xml version="1.0" encoding="utf-8"<em></strong>?>
|
||
</em><<strong>layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||
</strong> <strong> xmlns:bind="http://schemas.android.com/apk/res-auto"</strong>>
|
||
<<strong>data</strong>>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
</<strong>data</strong>>
|
||
<<strong>LinearLayout
|
||
android:orientation="vertical"
|
||
android:layout_width="match_parent"
|
||
android:layout_height="match_parent"</strong>>
|
||
<<strong>include layout="@layout/name"
|
||
bind:user="@{user}"</strong>/>
|
||
<<strong>include layout="@layout/contact"
|
||
bind:user="@{user}"</strong>/>
|
||
</<strong>LinearLayout</strong>>
|
||
</<strong>layout</strong>>
|
||
</pre>
|
||
|
||
<p>Here, there must be a <code>user</code> variable in both the <code>name.xml </code>and <code>contact.xml </code>layout files.</p>
|
||
<h3 id=expression_language>Expression Language</h3>
|
||
|
||
|
||
<h4 id=common_features>Common Features</h4>
|
||
|
||
<p>The expression language looks a lot like a Java expression. These are the same:</p>
|
||
<ul>
|
||
<li> Mathematical <strong><code>+ - / * %</code></strong>
|
||
<li> String concatenation <strong><code>+</code></strong>
|
||
<li> <code>L</code>ogical <strong><code>&& ||</code></strong>
|
||
<li> Binary <strong><code>&</code> <code>|</code> <code>^</code></strong>
|
||
<li> Unary <strong><code>+ - ! ~</code></strong>
|
||
<li> Shift <strong><code>>> >>> <<</code></strong>
|
||
<li> Comparison <strong><code>== > < >= <=</code></strong>
|
||
<li> <strong><code>instanceof</code></strong>
|
||
<li> Grouping <strong><code>()</code></strong>
|
||
<li> Literals - character, String, numeric, <strong><code>null</code></strong>
|
||
<li> Cast
|
||
<li> Method calls
|
||
<li> Field access
|
||
<li> Array access <strong><code>[]</code></strong>
|
||
<li> Ternary operator <strong><code>?:</code></strong>
|
||
</ul>
|
||
<p>Examples:</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text="@{String.valueOf(index + 1)}"
|
||
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
|
||
android:transitionName='@{"image_" + id}'</strong>
|
||
</pre>
|
||
|
||
|
||
<h4 id=missing_operations>Missing Operations</h4>
|
||
|
||
<p>A few operations are missing from the expression syntax that you can use in
|
||
Java.</p>
|
||
<ul>
|
||
<li> <strong><code>this</code></strong>
|
||
<li> <strong><code>super</code></strong>
|
||
<li> <strong><code>new</code></strong>
|
||
<li> Explicit generic invocation
|
||
</ul>
|
||
|
||
<h4 id=null_coalescing_operator>Null Coalescing Operator</h4>
|
||
|
||
<p>The null coalescing operator (<strong><code>??</code></strong>) chooses the left operand if it is not null or the right if it is null.</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text="@{user.displayName ?? user.lastName}"</strong>
|
||
</pre>
|
||
|
||
<p>This is functionally equivalent to:</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text="@{user.displayName != null ? user.displayName : user.lastName}"</strong>
|
||
</pre>
|
||
|
||
|
||
<h4 id=property_reference>Property Reference</h4>
|
||
|
||
<p>The first was already discussed in the <a href="#writing_your_first_data_binding_expressions">Writing your first data binding expressions</a> above: short form JavaBean references. When an expression references a
|
||
property on a class, it uses the same format for fields, getters, and
|
||
ObservableFields.</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text="@{user.lastName}"</strong>
|
||
</pre>
|
||
|
||
|
||
<h4 id=collections>Collections</h4>
|
||
|
||
<p>Common collections: arrays, lists, sparse lists, and maps, may be accessed
|
||
using the <code>[]</code> operator for convenience.</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="android.util.SparseArray"</strong>/>
|
||
<<strong>import type="java.util.Map"</strong>/>
|
||
<<strong>import type="java.util.List"</strong>/>
|
||
<<strong>variable name="list" type="List<String>"</strong>/>
|
||
<<strong>variable name="sparse" type="SparseArray<String>"</strong>/>
|
||
<<strong>variable name="map" type="Map<String, String>"</strong>/>
|
||
<<strong>variable name="index" type="int"</strong>/>
|
||
<<strong>variable name="key" type="String"</strong>/>
|
||
</<strong>data</strong>>
|
||
…
|
||
<strong>android:text="@{list[index]}"
|
||
</strong>…
|
||
<strong>android:text="@{sparse[index]}"
|
||
</strong>…
|
||
<strong>android:text="@{map[key]}"
|
||
</strong>
|
||
</pre>
|
||
|
||
|
||
<h4 id=string_literals>String Literals</h4>
|
||
|
||
<p>When using single quotes around the attribute value, it is easy to use double
|
||
quotes in the expression:</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text='@{map["firstName"]}'</strong>
|
||
</pre>
|
||
|
||
<p>It is also possible to use double quotes to surround the attribute value. When
|
||
doing so, String literals should either use the " or back quote (`).</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text="@{map[`firstName`}"
|
||
android:text="@{map["firstName"]}"</strong>
|
||
</pre>
|
||
|
||
|
||
<h4 id=resources>Resources</h4>
|
||
|
||
<p>It is possible to access resources as part of expressions using the normal
|
||
syntax:</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"</strong>
|
||
</pre>
|
||
|
||
<p>Format strings and plurals may be evaluated by providing parameters:</p>
|
||
<pre class=prettyprint>
|
||
<strong>android:text="@{@string/nameFormat(firstName, lastName)}"
|
||
android:text="@{@plurals/banana(bananaCount)}"</strong>
|
||
</pre>
|
||
|
||
<p>Some resources require explicit type evaluation.</p>
|
||
|
||
<table>
|
||
<tr>
|
||
<th>Type</th>
|
||
<th>Normal Reference</th>
|
||
<th>Expression Reference</th>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre class=prettyprint>
|
||
String[]</td>
|
||
<td>
|
||
@array</td>
|
||
<td>
|
||
@stringArray</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
int[]</td>
|
||
<td>
|
||
@array</td>
|
||
<td>
|
||
@intArray</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
TypedArray</td>
|
||
<td>
|
||
@array</td>
|
||
<td>
|
||
@typedArray</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
Animator</td>
|
||
<td>
|
||
@animator</td>
|
||
<td>
|
||
@animator</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
StateListAnimator</td>
|
||
<td>
|
||
@animator</td>
|
||
<td>
|
||
@stateListAnimator</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
</pre>
|
||
|
||
color <code>int</code></td>
|
||
<td>
|
||
<pre class=prettyprint>
|
||
@color</td>
|
||
<td>
|
||
@color</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
ColorStateList</td>
|
||
<td>
|
||
@color</td>
|
||
<td>
|
||
@colorStateList</td>
|
||
</tr>
|
||
</table>
|
||
|
||
</pre>
|
||
|
||
|
||
<h2 id="data_objects">Data Objects</h2>
|
||
|
||
|
||
<p>Any plain old Java object (POJO) may be used for data binding, but modifying a
|
||
POJO will not cause the UI to update. The real power of data binding can be
|
||
used by giving your data objects the ability to notify when data changes. There
|
||
are three different data change notification mechanisms, <code>Observable </code>objects, <code>ObservableField</code>s, and <code>observable collections</code>.</p>
|
||
|
||
<p>When one of these observable data object is bound to the UI and a property of
|
||
the data object changes, the UI will be updated automatically.</p>
|
||
|
||
<h3 id=observable_objects>Observable Objects</h3>
|
||
|
||
|
||
<p>A class implementing <code>android.databinding.Observable</code> interface will allow the binding to attach a single listener to a bound object
|
||
to listen for changes of all properties on that object.</p>
|
||
|
||
<p>The <code>Observable</code> interface has a mechanism to add and remove listeners, but notifying is up to
|
||
the developer. To make development easier, a base class, <code>BaseObservable,</code> was created to implement the listener registration mechanism. The data class
|
||
implementer is still responsible for notifying when the properties change. This
|
||
is done by assigning an <code>Bindable </code>annotation to the getter and notifying in the setter.</p>
|
||
|
||
<pre class=prettyprint>
|
||
<strong>private static class </strong>User <strong>extends </strong>BaseObservable {
|
||
<strong>private </strong>String <strong>firstName</strong>;
|
||
<strong>private </strong>String <strong>lastName</strong>;
|
||
@Bindable
|
||
<strong>public </strong>String getFirstName() {
|
||
<strong>return this</strong>.<strong>firstName</strong>;
|
||
}
|
||
@Bindable
|
||
<strong>public </strong>String getFirstName() {
|
||
<strong>return this</strong>.<strong>lastName</strong>;
|
||
}
|
||
<strong>public void </strong>setFirstName(String firstName) {
|
||
<strong>this</strong>.<strong>firstName </strong>= firstName;
|
||
notifyPropertyChanged(BR.firstName);
|
||
}
|
||
<strong>public void </strong>setLastName(String lastName) {
|
||
<strong>this</strong>.<strong>lastName </strong>= lastName;
|
||
notifyPropertyChanged(BR.lastName);
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>The <code>Bindable </code>annotation generates an entry in the BR class file during compilation. The BR
|
||
class file will be generated in the module package.If the base class for data classes cannot be changed, the <code>Observable</code> interface may be implemented using the convenient <code>PropertyChangeRegistry</code> to store and notify listeners efficiently.</p>
|
||
|
||
<h3 id=observablefields>ObservableFields</h3>
|
||
|
||
<p>A little work is involved in creating Observable classes, so developers who
|
||
want to save time or have few properties may use ObservableFields.
|
||
ObservableFields are self-contained observable objects that have a single
|
||
field. There are versions for all primitive types and one for reference types.
|
||
To use, create a public final field in the data class:</p>
|
||
<pre class=prettyprint>
|
||
<strong>private static class </strong>User <strong>extends </strong>BaseObservable {
|
||
<strong>public final </strong>ObservableField<String> <strong>firstName </strong>=
|
||
<strong>new </strong>ObservableField<>();
|
||
<strong>public final </strong>ObservableField<String> <strong>lastName </strong>=
|
||
<strong>new </strong>ObservableField<>();
|
||
<strong>public final </strong>ObservableInt <strong>age </strong>= <strong>new </strong>ObservableInt();
|
||
}
|
||
</pre>
|
||
|
||
<p>That's it! To access the value, use the set and get accessor methods:</p>
|
||
<pre class=prettyprint>
|
||
user.<strong>firstName</strong>.set(<strong>"Google"</strong>);
|
||
<strong>int </strong>age = user.<strong>age</strong>.get();
|
||
</pre>
|
||
|
||
|
||
<h3 id=observable_collections>Observable Collections</h3>
|
||
|
||
<p>Some applications use more dynamic structures to hold data. Observable
|
||
collections allow keyed access to these data objects.ObservableArrayMap is useful when the key is a reference type, such as String.</p>
|
||
|
||
<pre class=prettyprint>
|
||
ObservableArrayMap<String, Object> user = <strong>new </strong>ObservableArrayMap<>();
|
||
user.put(<strong>"firstName"</strong>, <strong>"Google"</strong>);
|
||
user.put(<strong>"lastName"</strong>, <strong>"Inc."</strong>);
|
||
user.put(<strong>"age"</strong>, 17);
|
||
</pre>
|
||
|
||
In the layout, the map may be accessed through the String keys:
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="android.databinding.ObservableMap"</strong>/>
|
||
<<strong>variable name="user" type="ObservableMap<String, Object>"</strong>/>
|
||
</<strong>data</strong>>
|
||
…
|
||
<<strong>TextView
|
||
android:text='@{user["lastName"]}'
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
<<strong>TextView
|
||
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|
||
<p>ObservableArrayList is useful when the key is an integer:</p>
|
||
<pre class=prettyprint>
|
||
ObservableArrayList<Object> user = <strong>new </strong>ObservableArrayList<>();
|
||
user.add(<strong>"Google"</strong>);
|
||
user.add(<strong>"Inc."</strong>);
|
||
user.add(17);
|
||
</pre>
|
||
|
||
<p>In the layout, the list may be accessed through the indices:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="android.databinding.ObservableList"</strong>/>
|
||
<<strong>import type="com.example.my.app.Fields"</strong>/>
|
||
<<strong>variable name="user" type="ObservableList<Object>"</strong>/>
|
||
</<strong>data</strong>>
|
||
…
|
||
<<strong>TextView
|
||
android:text='@{user[Fields.LAST_NAME]}'
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
<<strong>TextView
|
||
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|
||
|
||
<h2 id=generated_binding>Generated Binding</h2>
|
||
|
||
<p>The generated binding class links the layout variables with the Views within
|
||
the layout. As discussed earlier, the name and package of the Binding may be <a href="#custom_binding_class_names">customized</a>. The Generated binding classes all extend <code>android.databinding.ViewDataBinding</code>.</p>
|
||
<h3 id=creating>Creating</h3>
|
||
|
||
<p>The binding should be created soon after inflation to ensure that the View
|
||
hierarchy is not disturbed prior to binding to the Views with expressions
|
||
within the layout. There are a few ways to bind to a layout. The most common is
|
||
to use the static methods on the Binding class.The inflate method inflates the View hierarchy and binds to it all it one step.
|
||
There are versions that attach the View to its parent and that inflate without
|
||
attaching.</p>
|
||
<pre class=prettyprint>
|
||
MyLayoutBinding binding = MyLayoutBinding.<em>inflate</em>(<strong>this</strong>);
|
||
MyLayoutBinding binding = MyLayoutBinding.<em>inflate</em>(viewGroup);
|
||
</pre>
|
||
|
||
<p>If the layout was inflated using a different mechanism, it may be bound
|
||
separately:</p>
|
||
<pre class=prettyprint>
|
||
MyLayoutBinding binding = MyLayoutBinding.<em>bind</em>(viewRoot);
|
||
</pre>
|
||
|
||
<p>Sometimes the binding cannot be known in advance. In such cases, the binding
|
||
can be created using the DataBindingUtil class:</p>
|
||
<pre class=prettyprint>
|
||
ViewDataBinding binding = DataBindingUtil.<em>inflate</em>(context, layoutId,
|
||
parent, attachToParent);
|
||
ViewDataBinding binding = DataBindingUtil.<em>bindTo</em>(viewRoot, layoutId);
|
||
</pre>
|
||
|
||
|
||
<h3 id=views_with_ids>Views With IDs</h3>
|
||
|
||
<p>A public final field will be generated for each View with an ID in the layout.
|
||
The binding does a single pass on the View hierarchy, extracting the Views with
|
||
IDs. This mechanism can be faster than calling findViewById for several Views. For example:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>layout xmlns:android="http://schemas.android.com/apk/res/android"</strong>>
|
||
<<strong>data</strong>>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
</<strong>data</strong>>
|
||
<<strong>LinearLayout
|
||
android:orientation="vertical"
|
||
android:layout_width="match_parent"
|
||
android:layout_height="match_parent"</strong>>
|
||
<<strong>TextView android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
android:text="@{user.firstName}"
|
||
</strong> <strong>android:id="@+id/firstName"</strong>/>
|
||
<<strong>TextView android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
android:text="@{user.lastName}"</strong> <strong>android:id="@+id/lastName"</strong>/>
|
||
</<strong>LinearLayout</strong>>
|
||
</<strong>layout</strong>>
|
||
</pre>
|
||
|
||
Will generate a binding class with:
|
||
<pre class=prettyprint>
|
||
<strong>public final </strong>TextView <strong>firstName</strong>;
|
||
<strong>public final </strong>TextView <strong>lastName</strong>;
|
||
</pre>
|
||
|
||
<p>IDs are not nearly as necessary as without data binding, but there are still
|
||
some instances where access to Views are still necessary from code.</p>
|
||
<h3 id=variables>Variables</h3>
|
||
|
||
<p>Each variable will be given a accessor methods.</p>
|
||
<pre class=prettyprint>
|
||
<<strong>data</strong>>
|
||
<<strong>import type="android.graphics.drawable.Drawable"</strong>/>
|
||
<<strong>variable name="user" type="com.example.User"</strong>/>
|
||
<<strong>variable name="image" type="Drawable"</strong>/>
|
||
<<strong>variable name="note" type="String"</strong>/>
|
||
</<strong>data</strong>>
|
||
</pre>
|
||
|
||
<p>will generate setters and getters in the binding:</p>
|
||
<pre class=prettyprint>
|
||
<strong>public abstract </strong>com.example.User getUser();
|
||
<strong>public abstract void </strong>setUser(com.example.User user);
|
||
<strong>public abstract </strong>Drawable getImage();
|
||
<strong>public abstract void </strong>setImage(Drawable image);
|
||
<strong>public abstract </strong>String getNote();
|
||
<strong>public abstract void </strong>setNote(String note);
|
||
</pre>
|
||
|
||
|
||
<h3 id=viewstubs>ViewStubs</h3>
|
||
|
||
<p>ViewStubs are a little different from normal Views. They start off invisible
|
||
and when they either are made visible or are explicitly told to inflate, they
|
||
replace themselves in the layout by inflating another layout.</p>
|
||
|
||
<p>Because the ViewStub essentially disappears from the View hierarchy, the View
|
||
in the binding object must also disappear to allow collection. Because the
|
||
Views are final, a ViewStubProxy object takes the place of the ViewStub, giving
|
||
the developer access to the ViewStub when it exists and also access to the
|
||
inflated View hierarchy when the ViewStub has been inflated.</p>
|
||
|
||
<p>When inflating another layout, a binding must be established for the new
|
||
layout. Therefore, the ViewStubProxy must listen to the ViewStub's
|
||
OnInflateListener and establish the binding at that time. Since only one can
|
||
exist, the ViewStubProxy allows the developer to set an OnInflateListener on it
|
||
that it will call after establishing the binding.</p>
|
||
|
||
<h3 id=advanced_binding>Advanced Binding</h3>
|
||
|
||
|
||
<h4 id=dynamic_variables>Dynamic Variables</h4>
|
||
|
||
<p>At times, the specific binding class won't be known. For example, a
|
||
RecyclerView Adapter operating against arbitrary layouts won't know the
|
||
specific binding class. It still must assign the binding value during the
|
||
onBindViewHolder.</p>
|
||
|
||
<p>In this example, all layouts that the RecyclerView binds to have an "item"
|
||
variable. The BindingHolder has a getBinding method returning the <code>ViewDataBinding</code> base.</p>
|
||
<pre class=prettyprint>
|
||
<strong>public void </strong>onBindViewHolder(BindingHolder holder, <strong>int </strong>position) {
|
||
<strong>final </strong>T item = <strong>mItems</strong>.get(position);
|
||
holder.getBinding().setVariable(BR.item, item);
|
||
holder.getBinding().executePendingBindings();
|
||
}
|
||
</pre>
|
||
|
||
|
||
<h4 id=immediate_binding>Immediate Binding</h4>
|
||
|
||
<p>When a variable or observable changes, the binding will be scheduled to change
|
||
before the next frame. There are times, however, when binding must be executed
|
||
immediately. To force execution, use the executePendingBindings() method.</p>
|
||
<h2 id=attribute_setters>Attribute Setters</h2>
|
||
|
||
<p>Whenever a bound value changes, the generated binding class must call a setter
|
||
method on the View with the binding expression. The data binding framework has
|
||
ways to customize which method to call to set the value.</p>
|
||
<h3 id=automatic_setters>Automatic Setters</h3>
|
||
|
||
For an attribute, data binding tries to find the method setAttribute. The
|
||
namespace for the attribute does not matter, only the attribute name itself.
|
||
|
||
<p>For example, an expression associated with TextView's attribute <strong><code>android:text</code></strong> will look for a setText(String). If the expression returns an int, data
|
||
binding will search for a setText(int) method. Be careful to have the
|
||
expression return the correct type, casting if necessary.Note that data binding will work even if no attribute exists with the given
|
||
name. You can then easily "create" attributes for any setter by using data
|
||
binding. For example, support DrawerLayout doesn't have any attributes, but
|
||
plenty of setters. You can use the automatic setters to use one of these.</p>
|
||
<pre class=prettyprint>
|
||
<android.support.v4.widget.<strong>DrawerLayout
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"
|
||
app:scrimColor="@{@color/scrim}"
|
||
app:drawerListener="@{fragment.drawerListener}"/></strong>
|
||
</pre>
|
||
|
||
|
||
<h3 id=renamed_setters>Renamed Setters</h3>
|
||
|
||
<p>Some attributes have setters that don't match by name. For these methods, an
|
||
attribute may be associated with the setter through BindingMethods annotation.
|
||
This must be associated with a class and contains BindingMethod annotations,
|
||
one for each renamed method. For example, the <strong><code>android:tint</code></strong> attribute is really associated with setImageTintList, not setTint.</p>
|
||
<pre class=prettyprint>
|
||
@BindingMethods({
|
||
@BindingMethod(type = <strong>"android.widget.ImageView"</strong>,
|
||
attribute = <strong>"android:tint"</strong>,
|
||
method = <strong>"setImageTintList"</strong>),
|
||
})
|
||
</pre>
|
||
|
||
<p>It is unlikely that developers will need to rename setters; the android
|
||
framework attributes have already been implemented.</p>
|
||
<h3 id=custom_setters>Custom Setters</h3>
|
||
|
||
<p>Some attributes need custom binding logic. For example, there is no associated
|
||
setter for the <strong><code>android:paddingLeft</code></strong> attribute. Instead, setPadding(left, top, right, bottom) exists. A static
|
||
binding adapter method with the BindingAdapter annotation allows the developer
|
||
to customize how a setter for an attribute is called.</p>
|
||
|
||
<p>The android attributes have already had BindingAdapters created. For example,
|
||
here is the one for paddingLeft:</p>
|
||
<pre class=prettyprint></p>
|
||
@BindingAdapter(<strong>"android:paddingLeft"</strong>)
|
||
<strong>public static void </strong>setPaddingLeft(View view, <strong>int </strong>padding) {
|
||
view.setPadding(padding,
|
||
view.getPaddingTop(),
|
||
view.getPaddingRight(),
|
||
view.getPaddingBottom());
|
||
}
|
||
</pre>
|
||
|
||
<p>Binding adapters are useful for other types of customization. For example, a
|
||
custom loader can be called off-thread to load an image.</p>
|
||
|
||
<p>Developer-created binding adapters will override the data binding default
|
||
adapters when there is a conflict.</p>
|
||
|
||
<p>You can also have adapters that receive multiple parameters. </p>
|
||
<pre class=prettyprint>
|
||
@BindingAdapter(attributes = {<strong>"bind:imageUrl"</strong>, <strong>"bind:error"</strong>})
|
||
<strong>public static void </strong>loadImage(ImageView view, String url, Drawable error) {
|
||
Picasso.<em>with</em>(view.getContext()).load(url).error(error).into(view);
|
||
}
|
||
</pre>
|
||
|
||
<p>This adapter will be called if both <strong>imageUrl </strong>and <strong>error </strong>are used for an ImageView and <em>imageUrl </em>is a string and <em>error</em> is a drawable.</p>
|
||
<ul>
|
||
<li> Custom namespaces are ignore during matching.
|
||
<li> You can also write adapters for android namespace.
|
||
</ul>
|
||
|
||
<pre class=prettyprint>
|
||
<ImageView app:imageUrl=“@{venue.imageUrl}”
|
||
app:error=“@{@drawable/venueError}”/>
|
||
</pre>
|
||
|
||
|
||
<h2 id=converters>Converters</h2>
|
||
|
||
|
||
<h3 id=object_conversions>Object Conversions</h3>
|
||
|
||
<p>When an Object is returned from a binding expression, a setter will be chosen
|
||
from the automatic, renamed, and custom setters. The Object will be cast to a
|
||
parameter type of the chosen setter.</p><p>This is a convenience for those using ObservableMaps to hold data. for example:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>TextView
|
||
android:text='@{userMap["lastName"]}'
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|
||
<p>The userMap returns an Object and that Object will be automatically cast to
|
||
parameter type found in the setter <code>setText(CharSequence)</code>. When there may be confusion about the parameter type, the developer will need
|
||
to cast in the expression.</p>
|
||
<h3 id=custom_conversions>Custom Conversions</h3>
|
||
|
||
<p>Sometimes conversions should be automatic between specific types. For example,
|
||
when setting the background:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>View
|
||
android:background="@{isError ? @color/red : @color/white}"
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|
||
<p>Here, the background takes a <code>Drawable</code>, but the color is an integer. Whenever a <code>Drawable</code> is expected and an integer is returned, the <code>int</code> should be converted to a <code>ColorDrawable</code>. This conversion is done using a static method with a BindingConversion
|
||
annotation:</p>
|
||
<pre class=prettyprint>
|
||
@BindingConversion
|
||
<strong>public static </strong>ColorDrawable convertColorToDrawable(<strong>int </strong>color) {
|
||
<strong>return new </strong>ColorDrawable(color);
|
||
}
|
||
</pre>
|
||
|
||
<p>Note that conversions only happen at the setter level, so it is <strong>not allowed </strong>to mix types like this:</p>
|
||
<pre class=prettyprint>
|
||
<<strong>View
|
||
android:background="@{isError ? @drawable/error : @color/white}"
|
||
android:layout_width="wrap_content"
|
||
android:layout_height="wrap_content"</strong>/>
|
||
</pre>
|
||
|