If you’re implementing the MVVM architecture on Android, DataBinding is your best bet. MVVM is also the main trend in all front-end /iOS/Android space right now

  1. Less code
  2. Greater fault tolerance
  3. Faster iteration
  4. Higher readability

This article was reedited based on Kotlin in 2019

preface

  1. Do not attempt to replace DataBinding with LiveData; DataBinding is itself compatible with LiveData properties
  2. MVVM is superior to MVP regardless of project size
  3. This is the mainstream and the future

Enabling DataBinding will automatically generate classes in the build directory. Because it is integrated into AndroidStudio, it is compiled in real time without requiring you to manually compile, and supports most code completion.

apply plugin: "kotlin-kapt" // Kotlin using Databinding must be added

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

At the beginning

  • Databinding is not a replacement for ButterKnife or anything like thatfindByIdIt’s just a small auxiliary function, and I recommend using Kotlin to address this need;
  • For the most part, Databinding’s error tips are pretty good, and individual XML writing errors are easy to troubleshoot
  • I want to emphasize that in Xml@ {}Do only assignment or simple ternary operations or null detection and do not do complex operations, otherwise it violates the principle of decoupling.
  • The business logic should be in the Model as much as possible
  • ViewModelBelongs to a class automatically generated by DataBinding

MVP vs. MVVM disadvantage

  1. MVP implementation via interface callbacks results in poor code readability and inconsistent reading order
  2. MVP does not implement two-way data binding
  3. MVP implementations vary from person to person, and differences lead to poor reading
  4. MVP has more code than MVC and is decoupled by increasing the code volume, geometrically doubling the code volume of MVVM
  5. Any platform on the front end is starting to look like MVVM, and MVVM is one of the most mature applications in the Web space

I open source based on Kotlin and Databinding features of RecyclerView library: BRV, with unparalleled simplicity and MVVM features;

I usually project development essential framework

  1. The most powerful network request Net on Android
  2. The strongest list on Android (including StateLayout) BRV
  3. Android’s most powerful default page StateLayout
  4. JSON and long text log printing tool LogCat
  5. Support for asynchronous and global custom toast tool Tooltip
  6. Develop debug window tool DebugKit
  7. One line of code creates the transparent StatusBar StatusBar

layout

Layout file

<layout>
  
    <data>
        <variable
            name="user"
            type="com.liangjingkanji.databinding.pojo.UserBean"/>
    </data>

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.liangjingkanji.databinding.MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.userName}"
            />
        
    </RelativeLayout>
  
</layout>
Copy the code

layout

The root node of the layout must be

. The layout can contain only one View tag. You cannot include

directly

data

The contents of the tag are the DataBinding data. Only one data tag can exist.

variable

The

tag allows you to specify classes, which can then be used in the property values of the control

<data>
	<variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
</data>
Copy the code

DataBinding’s setxx() method allows you to set data for Variable. The name value cannot contain _ _

import

The second method (import), by default, imports the Java /lang class (String/Integer). You can use the static methods of the imported class directly.

<data>
  <! -- Import class -->
    <import type="com.liangfeizc.databindingsamples.basic.User" />
  <! -- Because User is already imported, we can abbreviate the class name -->
    <variable name="user" type="User" />
</data>
Copy the code

Using class

<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.userName}"
          />
<! --user is the name in the Variable tag, which can be customized, and then the class in type is used -->
Copy the code

Tip: User represents the class UserBean. You can use the methods and member variables in UserBean. If it is getxx(), it will be automatically identified as xx. Note that the android string cannot be used, otherwise it will report an error that cannot bind.

class

The tag has an attribute

that allows you to customize the name and path of the class generated by the DataBinding

<! -- Custom class name -->
<data class="CustomDataBinding"></data>

<! -- Custom build path and type -->
<data class=".CustomDataBinding"></data> <! -- automatically generate packages and classes under the package name -->
Copy the code

Tip: Note that there is no code auto-completion. Custom path Module/build/generated/source/apt/debug/databinding/directory, with almost no custom path

The default:

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // The ActivityMainBinding class is generated based on the layout filename (id+Binding).
    ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    UserBean userBean = new UserBean();
    userBean.setUserName("drake");

    // The setUser method is automatically generated from the name attribute of the Variable tagviewDataBinding.setUser(userBean); }}Copy the code

alias

Use the alias attribute if you need to import two classes with the same name.

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
Copy the code

include

You may need to pass variable values to include other layouts

<variable
          name="userName"
          type="String"/>.<include
         layout="@layout/include_demo"
         bind:userName="@{userName}"/>
Copy the code

include_demo

    <data>

        <variable
            name="userName"
            type="String"/>
    </data>. android:text="@{userName}"Copy the code

The two layouts are passed through include’s bind:< variable name > value. And they have to have the same variable

DataBinding does not support merge tag passing variables

Automatic layout properties

DataBinding has very good support for custom properties. As long as the View contains a setter method, you can use that property directly in the layout (this is because DataBinding’s library officially has many custom properties written for you).

public void setCustomName(@NonNull final String customName) {
    mLastName.setText("Daniel Wu");
  }
Copy the code

Then use it directly (but the IDE doesn’t have code completion)

app:customName="@{@string/wuyanzu}"
Copy the code

However, setter methods only support a single parameter. app: This namespace can be arbitrary

Data bidirectional binding

Data refresh view

BaseObservable

If you want the data to change and the view to change, you need to use the following two methods

There are two ways:

Inheritance BaseObservable

public class ObservableUser extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName(a) {
        return firstName;
    }

  // The annotation automatically generates an entry in the BR class in the build directory. The method name must start with GET
    @Bindable
    public String getLastName(a) {
        return lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName); // Manual refresh is required}}Copy the code
  • To simplify the usage, you simply need to inherit BaseObservable from the data model, and then call the notify() function to refresh the view each time the data changes. No annotations are required.

    observableUser.name
    observableUser.notifyChange()
    Copy the code
  • If you can’t inherit, you can also implement the interface. Just look at the interface implemented by BaseObservable for your own implementation or copy the code sample.

You can also listen for property change events

ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
  @Override
  public void onPropertyChanged(Observable observable, int i) {}});Copy the code

The property is called back twice the first time it is changed, and only once thereafter. If you use notifyChange(), you won’t get id(that is, I equals 0). use

NotifyPropertyChanged (I) will get the ID in the callback.

The difference between BaseObservable and Observable

  1. BaseObservable is a class that implements Observable and helps us implement thread-safe listeners.
  2. OnPropertyChangedCallback BaseObservable used PropertyChangeRegistry to execution
  3. So I don’t recommend that you implement Observable directly.

ObservableField

In this second approach, databinding defaults to a set of field types that implement the Observable interface

BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField<T>,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable<T extends Parcelable>,
ObservableShort,
ViewDataBinding
Copy the code

The sample

public class PlainUser {
  public final ObservableField<String> firstName = new ObservableField<>();
  public final ObservableField<String> lastName = new ObservableField<>();
  public final ObservableInt age = new ObservableInt();
}
Copy the code

For collection data types ObservableArrayMap/ObservableArrayLis/ObjservableMap collection data types

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName"."Google");
user.put("lastName"."Inc.");
user.put("age".17);
Copy the code

use

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data><TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
Copy the code

Tip:

  1. Also supportsObservableParcelable<Object>Serialized data type
  2. These two methods only update the view with the data, the data does not follow the view refresh.
  3. ObservableField also supports addOnPropertyChangedCallback to monitor the property change

This is also supported if the data is LiveData, and ViewDataBinding can set the life cycle.

View refresh data

Using the @= expression through an expression can automatically update the data when the view is refreshed, but the data must be modified in one of two ways to trigger the refresh

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textNoSuggestions"
    android:text="@={model.name}"/>
Copy the code

The big problem with this bidirectional binding is that it loops forever. A change in data (callback listener) triggers a change in view, which then triggers a change in data (callback listener again), and so on, setting the same data as a change.

So we need to determine whether the current changed data is the same as the old data

public class CustomBindingAdapter {

  @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) {
    CharSequence oldText = view.getText();

    if(! haveContentsChanged(text, oldText)) {return; // Do not refresh the view if the data does not change
    }
    view.setText(text);
  }


  // This tool class is taken from the official source code
  private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
    if ((str1 == null) != (str2 == null)) {
      return true;
    } else if (str1 == null) {
      return false;
    }
    final int length = str1.length();
    if(length ! = str2.length()) {return true;
    }
    for (int i = 0; i < length; i++) {
      if(str1.charAt(i) ! = str2.charAt(i)) {return true; }}return false; }}Copy the code

Tip:

  1. According to what I said above, the listener calls back at least twice (data -> view, view -> data).

  2. The following is not valid because the String parameter is passed as a reference type and the variable is not constant. We need to use equals().

    // This is the official source code. I don't know why this is written
    if (text == oldText || (text == null && oldText.length() == 0)) {
      return; 
    }
    
    / * * /Copy the code

    correct

    if (text == null || text.equals(oldText) || oldText.length() == 0) {
      return;
    }
    Copy the code

The bottom line is that if there are no default control properties to implement using two-way data binding you need to implement BindingAdapter annotations

annotations

DataBinding controls ViewModel class generation through annotations

@Bindable

Automatically refresh view for data updates. Data binding is mentioned later.

@BindingAdapter

Create an XML attribute and function, and then set the data in the attribute to enter the function.

The image loading framework facilitates this approach.

@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
  public static void loadImage(ImageView view, String url, Drawable error) {
    Glide.with(view.getContext()).load(url).into(view);
  }
Copy the code
  1. Modify method, require method mustpublic static
  2. The first argument must be the control or its parent
  3. Arbitrary method name
  4. This lastbooleanThe type is optional. You can ask whether all parameters need to be filled. True by default.
  5. ifrequireAllIf it is false, the attribute value you did not fill in will be null. Therefore, a non-null judgment is required.

Use:

<ImageView
           android:layout_width="match_parent"
           android:layout_height="200dp"
           app:error="@{@drawable/error}"
           wuyanzu:imageUrl="@{imageUrl}"
           app:onClickListener="@{activity.avatarClickListener}"
           />
Copy the code

As you can see, namespaces can be arbitrary, but if you define a namespace in an array of BindingAdapter you must comply fully

Such as:

// An annotation parameter is omitted.
@BindingAdapter({ "android:imageUrl", "error" })
  public static void loadImage(ImageView view, String url, Drawable error) {
    if(url == null) return;
    Glide.with(view.getContext()).load(url).into(view);
  }
Copy the code

Tip: If your data initialization is asynchronous. The callback method is called but the data is null(the default value for the member).

There are two approaches to Kotlin implementations

Singleton class + @jVMStatic annotation

object ProgressAdapter {

    @JvmStatic
    @BindingAdapter("android:bindName")
    fun setBindName(view: View, name:String){}}Copy the code

Top function

@BindingAdapter("android:bindName")
fun setBindName(view: View, name:String){}// It is recommended to use the top-level extension function because there are too many top-level functions affecting code completion. It can also be easily used in the code later

@BindingAdapter("android:bindName")
fun View.setBindName( name:String){}Copy the code

@BindingMethods

If you want to create an XML attribute and associate it with a function in the View (that is, the function is automatically called with the attribute value as an argument). You should annotate a class with @bindingMethods (the class can be unlimited and even an interface).

If @BindingAdapter creates a new function for the control to use, then BindingMethod directs the DataBinding to use the function of the control itself.

This annotation belongs to a container. The inner argument is an @bindingMethod array that can only be used to decorate the class;

Any class or interface. No functions need to be overridden

Official examples:

@BindingMethods({ @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"), @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"), @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"), })
public class ProgressBarBindingAdapter {}Copy the code

@BindingMethod

Note Parameter (Mandatory)

  1. Type: The bytecode is your control class
  2. Attribute: XML attributes
  3. Method: The function name is the name of the function in the control

Pay attention to

  • If the property name and@BindingAdapterAn error is reported when the XML attributes defined are the same
  • If the control class already has a function associated with the property you define (examplesetNameFunctions andandroid:nameProperty is associated with), the function is executed first

@BindingConversion

Property values are automatically cast

  1. Can only be modifiedpublic static Methods.
  2. There is no limit to any method name in any location
  3. DataBinding automatically matches the method decorated by the annotation and the type of the matching parameter
  4. The return value type must match the setter method of the property, and there can be only one parameter
  5. Requires that the attribute value be@ {}DataBinding expressions

Official examples:

public class Converters {
    @BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
        return new ColorDrawable(color);
    }
    @BindingConversion
    public static ColorStateList convertColorToColorStateList(int color) {
        returnColorStateList.valueOf(color); }}Copy the code

The Kotlin example I wrote

@BindingConversion
fun int2string(integer:Int):String{
    Log.d("Log"."(CusView.kt:92) int2string ___ integer = [$integer]. "")

    return integer.toString()
}
Copy the code

XML

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="m"
            type="com.example.architecture.Model" />

    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">


       <com.example.architecture.CusView
           android:bindName="@={m.age}"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" />


    </FrameLayout>

</layout>
Copy the code

I’m actually getting an error with this code because it involves bidirectional data binding and @bindingConversion only takes effect when the data is set to the view. If the function returns a type that does not match the type in the Model, an exception will be raised unless you change that function to a type match.

Or remove the = symbol and do not use two-way data binding

Android :text cannot convert an int to a string because it normally accepts an int(as a resourceID). Then submitted to the

android.content.res.Resources$NotFoundException: String resource ID #0xa
Copy the code

@InverseMethod

This annotation belongs to the new annotations of the inverse series provided by AndroidStudio3, all of which are for bidirectional data binding.

This annotation @inversemethod can be used to solve data transformation problems when data and view data are inconsistent

For example, if the data model stores the user’s ID but the view displays the user name instead of the ID (the data and view are not of the same type), we need to convert between the two.

We need two functions: the function that sets the data to the view is called set/and the function that sets the view changes to the data is called GET

  • Both set and get must have at least one argument
  • The argument must correspond to the return value of another function.

A simple example:

Convert between user ID and user name. Stores the ID but displays the user name

class Model {
  
  var name = "Designer"
  
   @InverseMethod("ui2data")
    fun data2ui(a):String{

        return "Takeshi Kaneshiro, designer"
    }

    fun ui2data(a):String{
        return "Daniel Wu, designer"}}Copy the code

use

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="m"
            type="com.example.architecture.Model" />

    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">


       <com.example.architecture.CusView
           android:text="@{m.data2ui(m.name)}"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" />

    </FrameLayout>

</layout>
Copy the code

@InverseBindingAdapter

Parameters:

  • String attributeAttribute value (required)
  • String eventThis parameter is mandatory. The default value is equal to<attribute>AttrChanged

He works with @BindingAdapter to implement two-way data binding

Full bidirectional data binding requires three functions

  1. Set (data to view)
  2. Get (view to data)
  3. Notify Databinding that the Databinding view has been refreshed to update the data (Model)

The set function, we’ve already written it before

@BindingAdapter("android:bindName")
fun TextView.setBindName(name:String?).{
    if(name.isNullOrEmpty() && name ! = text) { text = name } }Copy the code

The get function

@InverseBindingAdapter(attribute = "android:bindName", event = "cus_event")
fun TextView.getBindName(a):String{

	// Here you can process the data on the view and finally set it to the Model layer

    return text.toString()
}
Copy the code
  • No more parameters are allowed
  • The return value type must be the bound data type

To notify the Databinding to start setting the Model layer after the notify view changes, use @bindingAdapter (InverseBindingListener).

@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){

  // This function is used to listen to TextWatch official source code
   doAfterTextChanged {
       inverseBindingListener.onChange() // This line of code executes to notify the data refresh}}Copy the code

InverseBindingListener is an interface that has only one function and is required as an argument to the notify function.

public interface InverseBindingListener {
    /** * Notifies the data binding system that the attribute value has changed. */
    void onChange(a);
}
Copy the code

@InverseBindingMethods

Is similar with @ BindingMethods

But @inverseBindingMethods is view change data (get), and BindingMethods is data to view (set)

parameter

public @interface InverseBindingMethod {

    /** * control class bytecode */
    Class type(a);

    /** * Custom properties */
    String attribute(a);

    /** * Nitify function name is the function used to notify data updates */
    String event(a) default "";

    /** * Specifies the function name of the control itself. If omitted, it automatically generates {attribute}AttrChange */
    String method(a) default "";
}
Copy the code

If BindingMethods correlate setter methods with custom properties, InverseBindingMethods correlate getter methods with custom properties.

Setters are for updating views, getters are for updating data

There is one more function than @bindingMethods, the notify function for notifying updates

@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){

   doAfterTextChanged {
       inverseBindingListener.onChange()
   }

}
Copy the code

Example:

@InverseBindingMethods( InverseBindingMethod( type = CusView::class, attribute = "android:bindName", method = "getName", event = "cus_event" ) )
object Adapter {

}
Copy the code
  • ifattributeIf the attribute value belongs to a nonexistent attribute, you need to create another oneBindingAdapterCustom properties to handle.

View under the generation class view update data implementation source code

private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
  @Override
  public void onChange(a) {
    // Inverse of data.name
    // is data.setName((java.lang.String) callbackArg_0)
    java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv);  
    // Get the attribute of the change
    // localize variables for thread safety
    // data ! = null
    boolean dataJavaLangObjectNull = false;
    // data.name
    java.lang.String dataName = null;
    // data
    com.liangjingkanji.databinding.Bean data = mData; // Get the datadataJavaLangObjectNull = (data) ! = (null);
    if (dataJavaLangObjectNull) {
      data.setName(((java.lang.String) (callbackArg_0))); // Store to data}}};Copy the code

So if you don’t override the Inverse data change method you won’t be able to get the view to notify the data refresh.

// This method is called when the layout is bound
    @Override
    protected void executeBindings(a) {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String dataName = null;
        com.liangjingkanji.databinding.Bean data = mData;

        if ((dirtyFlags & 0x1aL) != 0) {



                if(data ! =null) {
                    // read data.namedataName = data.getName(); }}// batch finished
        if ((dirtyFlags & 0x1aL) != 0) {
            // api target 1

            com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName);
        }
        if ((dirtyFlags & 0x10L) != 0) {
            // api target 1

          // The important thing about this code is that it passes the listener created above into the setTextWatcher method
            com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr); }}Copy the code

conclusion

The @bindingBuildinfo and @Untaggable annotations are used by DataBinding when it automatically generates Java classes.

  • Bindable

    Set the data refresh view. The BR ID is automatically generated

  • BindingAdapter

    Set custom properties. You can override the system’s original attributes

  • BindingMethod/BindingMethods

    Associates custom properties to the original setter method of the control

  • BindingConversion

    If the property does not match the type parameter, the conversion is automatically made based on the type parameter matching to the method decorated by the annotation

  • InverseMethod

    Responsible for implementing the transformation between views and data

  • InverseBindingAdapter

    View to notify the data refresh

  • InverseBindingMethod/InverseBindingMethods

    View to notify the data refresh (if an existing getter method is available)

  • The BindingMethods series takes precedence over the BindingAdapter series

  • All annotation functionality takes effect based on the XML attribute value being a Databinding expression (that is, @{})

It is recommended to refer to the official implementation source:

DataBindingAdapter

expression

This refers to the expressions used in XML files (for assigning variables). In addition to executing methods, @{} can also write expressions, and supports some specific expressions

  • Arithmetic + – / * %
  • String merge +
  • Logic && | |
  • Binary & | ^
  • One yuan + -! ~
  • Shift >> >>> <<
  • Compare == > < >= <=
  • Instanceof
  • Grouping ()
  • Literal – character, String, numeric, NULL
  • Cast
  • The method call
  • Field visit
  • Array access []
  • Three yuan? :

Avoid null Pointers

The value of variable does not have a null pointer exception even if it is set to null or not.

This is because officials have overwritten many of the properties with DataBinding’s @bindingAdapter annotation. And carried out inside empty processing.

<variable
	name="userName"
	type="String"/>

.....

android:text="@{userName}"
Copy the code

No null pointer exception occurs.

dataBinding.setUserName(null);
Copy the code

It also supports characteristic non-empty multivariate expressions

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

Is equivalent to the

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

Again, we need to be careful that the array is out of bounds

A collection of

The collection does not belong to java.lang*, so you need to import the full path.

<variable
          name="list"
          type="java.util.List&lt;String&gt;"/>

<variable
          name="map"
          type="java.util.Map<String, String>"/>
Copy the code

This will give you an error

Error: The value of the "type" attribute associated with the element type" variable" cannot contain the '<' character.Copy the code

Because the < symbol needs to be escaped.

Commonly used escape characters

Space & have spent The & # 160;

< < number & lt; & # 60;

> > < span style = “box-sizing: border-box; The & # 62;

&&amp; & # 38; “Quotation marks” & # 34; Apostrophes &apos; & # 39; * * * &times; The & # 215; Divide sign &divide; The & # 247;

Correct term

<variable
          name="list"
          type="java.util.List&lt;String&gt;"/>

<variable
          name="map"
          type="java.util.Map&lt;String, String&gt;"/>
Copy the code

Both collections and arrays can use [] to get their elements

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

string

If you want to use strings in @{}, you can do it in one of three ways

The first:

Android :text='@{" "}'Copy the code

The second:

Android :text="@{' Daniel '}"Copy the code

The third:

android:text="@{@string/user_name}"
Copy the code

@color or @drawable is also supported

Format string

So let’s define

in strings

<string name="string_format">Name: %s Gender: %s</string>
Copy the code

You can then use DataBinding expressions

android:text="@{@string/string_format(' Daniel ', 'Male')}"
Copy the code

Output content:

Name: Daniel Wu Gender: MaleCopy the code

The default value

If the Variable has not been copied, it will be displayed with the default value.

android:text="@{user.integral, default=`30`}"
Copy the code

context

DataBinding itself provides a Variable called context. You can just use getContext() which is the same as the View.

android:text="@{context.getApplicationInfo().toString()}"
Copy the code

Referencing other controls

<TextView
          android:id="@+id/datingName"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerVertical="true"
          android:layout_marginLeft="8dp"
          android:layout_toRightOf="@id/iv_dating"
          android:text="Activity"
          />

/...
<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_centerVertical="true"
          android:layout_marginLeft="8dp"
          android:layout_toRightOf="@id/iv_order"
          android:text="@{datingName.text}"
          />
Copy the code

References to control ids that contain _ can be ignored directly. For example, tv_name directly writes tvName.

Thanks lambda for pointing out the error

References can be made in any order

Use the Class

If you want to pass a Class as a parameter, that Class cannot be used directly through a static import. Needs to be used as a field constant

The callback function

DataBinding also supports binding function parameter types in XML, and is also a Lambda and higher-order function type, which is more advanced than Java.

object

That is, objects are directly used in XML in the same way as attributes. To do this, you must first manually create an object. It’s a little troublesome.

Higher-order functions

Create custom properties

object EventDataBindingComponent {

    /** * Can be used in the Model to handle the UI when binding views, because breaking the rule of decoupling views and logic is not recommended * this will cause inconvenient unit testing of business logic **@seeOnBindViewListener This interface supports generically defining specific views * *@receiver View
     * @param block OnBindViewListener<View>
     */
    @JvmStatic
    @BindingAdapter("view")
    fun View.setView(listener: OnBindViewListener) {
        listener.onBind(this)}}Copy the code

The interface used above

interface OnBindViewListener {
    fun onBind(v: View)
}
Copy the code

Higher-order functions


      
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
                  name="v"
                  type="com.liangjingkanji.databinding.MainActivity"/>
    </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="Daniel Wu, designer"
                  android:onClick="@{v::click}"/>

    </LinearLayout>
</layout>
Copy the code

Using higher-order functions in XML matches the following rules

  1. Function parameters of BindingAdapter are required to be an interface. Function type parameters of Kotlin are not supported
  2. The interface allows only one function
  3. The method signature of the interface (return value | parameters) and the transfer of higher-order functions

Lambda

Higher-order functions do not allow custom passing of arguments (otherwise you would need to modify the interface). So you can use Lambda for control.

Create a function with multiple arguments

fun onBinding(v:View, name:String){
  Log.d("Log"."(MainActivity.kt:45)    this = [$v]  name = [$name]. "")}Copy the code

XML using

view="@{(view) -> v.onbinding (view)}"
Copy the code

If no parameter is used

view="@{() -> v.onbinding (' ')}"Copy the code

ViewDataBinding

All automatically generated DataBinding classes are inherited from this class. So they all have methods of that class

void	addOnRebindCallback(OnRebindCallback listener)
// Add a binding listener that can be called back when the Variable is set

void	removeOnRebindCallback(OnRebindCallback listener)
// Delete the binding listener

View	getRoot(a)
// Return the bound view object
  
abstract void	invalidateAll(a)
// Invalidate all expressions and reset the expression immediately. It will refire the OnRebindCallback (think of it as a reset)


abstract boolean	setVariable(int variableId, Object value)
// The variable can be set according to the field ID

void	unbind(a)
// Unbind the binding. The UI will not change according to the data, but the listener will still fire
Copy the code

Here are three methods to focus on:

abstract boolean	hasPendingBindings(a)
// Return true when the UI needs to change according to the current data.

void	executePendingBindings(a)
// Force the UI to refresh the data immediately,
Copy the code

When you change the data (when you set the Observable), the UI will be refreshed immediately, but not until the next frame, with a delay. HasPendingBindings () will return true during this time. ExecutePendingBindings () can be called immediately if you want to refresh the UI synchronously (or immediately).

OnRebindCallback

The listener can listen to the life cycle of the layout binding

    mDataBinding.addOnRebindCallback(new OnRebindCallback() {
      /** * before binding *@param binding
       * @returnReturning true will bind the layout; returning false will unbind */
      @Override public boolean onPreBind(ViewDataBinding binding) {
        return false;
      }

      /** * calls back to this method if unbind (depending on the return value of onPreBind) *@param binding
       */
      @Override public void onCanceled(ViewDataBinding binding) {
        super.onCanceled(binding);
      }

      /** * Binding complete *@param binding
       */
      @Override public void onBound(ViewDataBinding binding) {
        super.onBound(binding); }});Copy the code

OnPropertyChangedCallback

DataBinding also has a data change listener that listens for setting events for a Variable

mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

  /** * will call back when DataBinding sets the data@paramSender DataBinding generated class *@paramPropertyId Id of a Variable */
  @Override public void onPropertyChanged(Observable sender, int propertyId) {
    ActivityMainBinding databinding = (ActivityMainBinding) sender;
    switch (propertyId) {
      case BR.data:
        Log.d("Log"."(MainActivity.java:54) ___ Result = " + databinding.getData().getName());
        break;
      case BR.dataSecond:

        break; }}});Copy the code

DataBindingUtil

DataBinding can bind not only to activities but also to views


/ / view
static <T extends ViewDataBinding> T	bind(View root)

static <T extends ViewDataBinding> T	bind(View root, DataBindingComponent bindingComponent)

  
/ / layout
static <T extends ViewDataBinding> T	inflate(LayoutInflater inflater, 
                                                int layoutId, 
                                                ViewGroup parent, 
                                                boolean attachToParent, DataBindingComponent bindingComponent) / / component

static <T extends ViewDataBinding> T	inflate(LayoutInflater inflater,
                                                int layoutId, 
                                                ViewGroup parent, 
                                                boolean attachToParent)

// activity
static <T extends ViewDataBinding> T	setContentView(Activity activity, 
                                                       int layoutId)

static <T extends ViewDataBinding> T	setContentView(Activity activity,
                                                       int layoutId, DataBindingComponent bindingComponent)
Copy the code

There are two less common methods that retrieve whether the view is bound or not and return NUL if not

static <T extends ViewDataBinding> T	getBinding(View view)

// getBinding is different from getBinding. If the view is not bound, it checks whether the parent container is bound
static <T extends ViewDataBinding> T	findBinding(View view)
Copy the code

Other methods

// Return a string type based on the id of the BR passed. May be used for log output
static String	convertBrIdToString(int id)
Copy the code

For example, if br.name corresponds to 4, you can use this method to convert 4 to “name”

DataBindingComponent

By default, BindingAdapter annotations are available for all XML attributes. These custom properties can be switched by specifying different DatabindingComponents.

Create DatabindingComponent:

  1. Create a custom class that contains functions that use @BindingAdapter without static functions.

    AndroidStudio will automatically generate the DatabindingComponnent interface

  2. Create a derived DatabindingComponent class that will prompt you for a method to override. Not if you omit the first step.

  3. Set your custom derived classes to the Databinding using the DataBindingUtils tool, which includes global defaults and singletons.

The first step

class PinkComponent {

    @BindingAdapter("android:bindName")
    fun TextView.setBindName(name:String?).{

        if(! name.isNullOrEmpty() && name ! = text) { text ="Data body"}}@BindingAdapter("android:bindNameAttrChanged")
    fun TextView.notifyBindName(inverseBindingListener: InverseBindingListener){

        doAfterTextChanged {
            inverseBindingListener.onChange()
        }

    }

    @InverseBindingAdapter(attribute = "android:bindName")
    fun TextView.getBindName(a):String{

        return text.toString()
    }
}
Copy the code

The second step

class CusComponent : DataBindingComponent {

    override fun getPinkComponent(a): PinkComponent {
        return PinkComponent() // Null cannot be returned here}}Copy the code

The third step

Setting the default components is all set by the DataBindingUtils, but the methods vary

static void	setDefaultComponent(DataBindingComponent bindingComponent)

static DataBindingComponent	getDefaultComponent(a)
Copy the code

These Settings must be set before binding the view, and are global by default, and only need to be set once.

static <T extends ViewDataBinding> T	setContentView(Activity activity,
                                                       int layoutId, DataBindingComponent bindingComponent)
Copy the code

If you don’t execute setDefaultComponent then choose to pass it in as a function, pass it in every time otherwise it will report an error.

DatabindingComponent can only use @bindingAdapter annotations

Pay attention to

  1. You can use include but not as a root layout. Merge is not available
  2. If you don’t automatically generate the DataBinding class, you can write a variable(or make Module)
  3. Even if you don’t bind the data (you might do so on the success of the network request), the data will be bound as soon as the view is created. At this point the data is an empty object. The fields of an empty object will also have default values (if String defaults to NULL, TextView will display NULL); And if you use ternary expressions, all ternary expressions for empty objects are false; Therefore, it is recommended not to consider the empty object case;
  4. If you give a custom property that requires a value of type Boolean (BindingAdapter) assign a function that returns false with a null pointer;

Recommend a plug-in

DataBindingSupport

Use the shortcut key (Alt + Enter) to automatically create expressions and nodes in the XML layout. AS4 is invalid

DataBindingConvert

Use the shortcut keys to quickly layout the package as layout, AS4 available