Full instructions for DataBinding

If you want to implement MVVM architecture in Android, DataBinding is your best choice MVVM is also the mainstream development direction in all front-end / iOS/Android fields

  1. Less code
  2. More robust fault tolerance
  3. Faster iteration speed
  4. Higher readability

This article and 2019 are based on Kotlin re editing

preface

  1. Do not attempt to use LiveData instead of databinding. Databinding itself is compatible with LiveData properties
  2. No matter the project size, MVVM is better than M*3 This is the mainstream and the future

Enabling DataBinding will automatically generate classes in the build directory Because it is integrated into Android studio, you don't need to compile manually. It will compile in real time, and supports most code completion

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

android{
  /.../
      dataBinding {
        enabled = true;
    }
}

start

  • Databinding is not a substitute for findById such as ButterKnife, but just a small auxiliary function. I recommend using Kotlin to solve this requirement;
  • In most cases, the error prompt of Databinding is perfect, and individual XML writing errors are easy to check
  • I want to emphasize that @{} in Xml only does assignment or simple ternary operation or space judgment, and do not do complex operation, otherwise it violates the decoupling principle
  • Business logic should be in the Model as much as possible
  • ViewModel belongs to the class automatically generated by DataBinding

Disadvantages of MVP over MVVM

  1. MVP is implemented through interface callback, resulting in poor code readability and incoherent reading order
  2. MVP cannot implement bidirectional data binding
  3. The implementation of MVP varies from person to person, and the difference leads to poor reading
  4. MVP has more code than MVC. It is decoupled by increasing the code, which is geometrically doubled than MVVM
  5. Any front-end platform starts to tend to MVVM, and MVVM in the web field is the most mature application

I open source a RecyclerView Library Based on Kotlin and Databinding features: BRV, which has unparalleled simplicity and MVVM features;

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>

layout

The layout root node must be < layout > At the same time, layout can only contain one View tag Cannot contain < merge > directly

data

The content of < data > tag is the data of DataBinding Only one data tag can exist

variable

The < variable > tag allows you to specify the class, which can then be used in the property value of the control

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

You can set data for a Variable through the setxx() method of DataBinding name value cannot contain_ Underline

import

The second writing method (import) imports the class (String/Integer) under the java/lang package by default You can directly use the static methods of the imported class

<data>
  <!--Import class-->
    <import type="com.liangfeizc.databindingsamples.basic.User" />
  <!--because User Already imported, So you can abbreviate the class name-->
    <variable name="user" type="User" />
</data>

Use class

<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.userName}"
          />
<!--user Is in Variable In label name, You can customize it at will, Then it will be used type Classes in-->

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

class

The < data > tag has an attribute < class > to customize the class name and path generated by DataBinding

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

<!--Custom build path and type-->
<data class=".CustomDataBinding"></data> <!--Automatically generate packages and classes under package names-->

Tip: note that there is no automatic code completion The user-defined path is in the Module/build/generated/source/apt/debug/databinding / directory. Basically, the user-defined path is not required

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 file name (id+Binding)
    ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

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

    // The setUser method is automatically generated according to the name attribute of the Variable tag
    viewDataBinding.setUser(userBean);
  }
}

alias

< variable > tag if you need to import two classes with the same name, you can use the alias attribute (alias attribute)

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

include

You may need to pass the value of a variable when including other layouts

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

....

<include
         layout="@layout/include_demo"
         bind:userName="@{userName}"/>

include\_demo

    <data>

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

...

android:text="@{userName}"

The two layouts are passed through the bind: < variable name > value of include And both must have the same variable

DataBinding does not support passing variables with merge tags

Auto layout properties

DataBinding supports user-defined properties very well. As long as the setter method is included in the View, you can directly use this property in the layout (this is because many user-defined properties have been written for you in the DataBinding Library)

public void setCustomName(@NonNull final String customName) {
    mLastName.setText("Wu Yanzu");
  }

Then use it directly (but the IDE has no code completion)

app:customName="@{@string/wuyanzu}"

However, the setter method only supports a single parameter app: this namespace is optional

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:

Inherit BaseObservable

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

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

  // The annotation will automatically generate an entry in the BR class of the build directory. It is required that the method name must start with get
    @Bindable
    public String getLastName() {
        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 required
    }
}
  • Simplified usage only requires data model to inherit BaseObservable, and then notify() function can refresh the view after each change of data. No comments are required observableUser.name observableUser.notifyChange()
  • If you can't inherit, you can also implement the interface View the interface implemented by BaseObservable. You can implement it yourself or copy the code example

You can also listen for property change events

ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
  @Override
  public void onPropertyChanged(Observable observable, int i) {

  }
});

The first time the property is changed, it will be called back twice, and then only once If you use notifyChange(), you will not get an ID (i.e. I equals 0) use

notifyPropertyChanged(i) can get the id in the callback

Difference between BaseObservable and Observable

  1. BaseObservable is a class that implements Observable and helps us realize the thread safety of listener
  2. BaseObservable uses PropertyChangeRegistry to execute OnPropertyChangedCallback
  3. So I don't recommend that you directly implement Observable

ObservableField

This is the second method. databinding implements a series of field types that implement the Observable interface by default

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

Example

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

For collection data types, such as observablearraymap / observablearraylis / observaservablemap

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

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"/>

Tip:

  1. It also supports observableparcelable < Object > serialized data types
  2. The above two methods only update the view with the data, and the data will not refresh with the view
  3. ObservableField also supports addOnPropertyChangedCallback to listen for property changes

If the data is LiveData, it is also supported, and ViewDataBinding can set the life cycle

View refresh data

Using the @ = expression through the expression can automatically update the data when the view is refreshed, but the refresh can be triggered only when the data is modified in the following two ways

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

A big problem with this two - way binding is that it will loop The data change (callback listener) triggers the view change, and then the view will trigger the data change (callback listener again), and then keep cycling. Setting the same data is also regarded as data change

Therefore, we need to judge whether the current changed data is equivalent to 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; // If the data does not change, the view will not be refreshed
    }
    view.setText(text);
  }


  // This tool class is intercepted 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;
  }
}

Tip:

  1. According to what I said above, the listener calls back at least twice (data - > view, view - > data)
  2. The following is invalid because the variable passed by the String parameter belongs to the reference type and is not a constant. You need to use equals()
// This paragraph intercepts the official source code. I don't know why it sb says if (text = = oldtext | (text = = null & & oldtext. Length() = = 0)) {return;}/**/
correct
if (text == null || text.equals(oldText) || oldText.length() == 0) {   return; }

The summary is that if there is no default control property and two-way data binding is used, you need to implement the BindingAdapter annotation yourself

annotation

DataBinding controls the class generation of ViewModel through annotations

@Bindable

Used to refresh the view automatically when updating data The data binding mentioned later

@BindingAdapter

Create an XML attribute and function, and then set it in the attribute. The data operation will enter the function

The picture loading framework can facilitate the use of this method

@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
  public static void loadImage(ImageView view, String url, Drawable error) {
    Glide.with(view.getContext()).load(url).into(view);
  }
  1. Modify the method. The method must be public static
  2. The first parameter must be a control or its parent class
  3. Method name optional
  4. Finally, the boolean type is an optional parameter You can ask whether all parameters need to be filled in The default is true
  5. If requireAll is false, the property value you did not fill in will be null Therefore, we need to make non empty judgment

use:

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

You can see that the namespace can be arbitrary, but if you define a namespace in the BindingAdapter array, you must fully comply with it

For example:

// An annotation parameter is omitted here   
@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);
  }

Tip: if your data initialization is asynchronous The method will be called back, but the data is null (member default) Therefore, we must first carry out air judgment

There are two ways to implement Kotlin

Singleton class + @ JvmStatic annotation

object ProgressAdapter {

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

    }
}

Top level function

@BindingAdapter("android:bindName")
fun setBindName(view: View, name:String){

}

// Because too many top-level functions affect code completion, it is recommended to use top-level extension functions, which can also be easily used in the code later

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

@BindingMethods

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

If @ BindingAdapter is to create a new function function for the control, then BindingMethod is to guide DataBinding to use the function of the control itself

The annotation belongs to a container The internal parameter is an array of @ BindingMethod, which can only be used to decorate classes;

Any class or interface does not need to override any function

Official example:

@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 {
}

@BindingMethod

Annotation parameter (required)

  1. type: bytecode is your control class
  2. attribute: XML attribute
  3. method: function name is the name of the function in the control

be careful

  • If the attribute name is the same as the XML attribute defined by @ BindingAdapter, an error will be reported
  • If a function associated with the property you defined already exists in the control class (for example, setName function and android:name property are associated), the function will be executed first

@BindingConversion

Property values are automatically type converted

  1. Only public static methods can be modified
  2. There is no limit to any method name anywhere
  3. DataBinding automatically matches the method and parameter types modified by the annotation
  4. The return value type must match the property setter method, and there can only be one parameter
  5. Property value is required to be a @{} DataBinding expression

Official example:

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

Kotlin example I wrote

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

    return integer.toString()
}

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>

My code will actually report an error because it involves two-way data binding, and @ BindingConversion will only take effect when the data view is set However, if it is view setting data, other functions (get) will be used. If the type returned by this function does not match the type in the Model, an exception will be reported unless you change that function to type matching

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

android:text cannot convert int to string because it can normally receive int (as resourceID) Then I'll report

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

@InverseMethod

This annotation belongs to the new annotation of the inverse series provided after Android studio 3. All of them are aimed at two-way data binding

This annotation @ InverseMethod can be used to solve the problem of data conversion when the data and view data are not unified

For example, the data model stores the user's id, but the view displays the user name instead of the id (the data and view types are inconsistent), so we need to convert between them

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

  • Both set and get must have at least one parameter
  • Its own parameters must correspond to the return value of another function (otherwise it is called conversion)

Simple example:

Convert between user id and user name The id is stored, but the user name is displayed when it is displayed

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

        return "Designer Jin Chengwu"
    }

    fun ui2data():String{
        return "Designer Wu Yanzu"
    }
}

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>

@InverseBindingAdapter

Parameters:

  • String attribute attribute value (required)
  • String event is not required. The default value is equal to < attribute > attrchanged

He cooperates with @ BindingAdapter to implement bidirectional data binding

Full bidirectional data binding requires three functions

  1. set (data to view)
  2. get (view to data)
  3. Notify (notify that the data binding view has been refreshed and the data model can be updated)

The set function has been written before

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

get function

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

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

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

After the notify function view changes, you need to notify Databinding to start setting the Model layer. You also need to use @ BindingAdapter. The difference is that the parameter can only be InverseBindingListener

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

  // This function monitors the official source code of TextWatch. Of course, different listeners have different needs
   doAfterTextChanged {
       inverseBindingListener.onChange() // This line of code is executed to notify the data refresh
   }

}

InverseBindingListener is an interface with only one function. It is a necessary parameter of the notify function

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

@InverseBindingMethods

Similar to @ BindingMethods

However, @ InverseBindingMethods is the view change data (get function), while BindingMethods is the data to view (set function)

parameter

public @interface InverseBindingMethod {

    /**
     * Class bytecode of the control
     */
    Class type();

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

    /**
     * nitify The name of the function is the function used to notify data updates
     */
    String event() default "";

    /**
     * The function name of the control itself. If omitted, it will be automatically generated as {attribute} attribhange
     */
    String method() default "";
}

If BindingMethods are associated setter methods and custom attributes, invertebindingmethods are associated getter methods and custom attributes;

The setter is used when updating the view, and the getter method is used when updating the data

There is one more function than @ BindingMethods, that is, the notify function, which is used to notify updates

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

   doAfterTextChanged {
       inverseBindingListener.onChange()
   }

}

Example:

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

}
  • If the attribute value belongs to an attribute that does not exist, you need to create another BindingAdapter custom attribute to handle it

View the implementation source code of view update data in the generated class

private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
  @Override
  public void onChange() {
    // 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 attributes of 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 data

    dataJavaLangObjectNull = (data) != (null);
    if (dataJavaLangObjectNull) {
      data.setName(((java.lang.String) (callbackArg_0))); // Store to data
    }
  }
};

Therefore, if you do not override the data change method of Inverse, you will not be able to let the view notify the data refresh

// This party * * * calls back when binding the layout
    @Override
    protected void executeBindings() {
        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.name
                    dataName = 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 focus is on this code. Pass 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);
        }
    }

summary

@The BindingBuildInfo and @ Untaggable annotations are used when DataBinding automatically generates Java classes

  • Bindable Set data refresh view Automatically generated BR ID
  • BindingAdapter Set custom properties You can overwrite the original system properties
  • BindingMethod/BindingMethods Associate the custom property to the original setter method of the control
  • BindingConversion If the attribute cannot match, the type parameter will be automatically converted according to the method that the type parameter matches to the annotation
  • InverseMethod Responsible for the transformation between view and data
  • InverseBindingAdapter View notifies of data refresh
  • InverseBindingMethod/InverseBindingMethods The view notifies data refresh (if there are existing getter methods available)
  • The BindingMethods family has a higher priority than the BindingAdapter family
  • All annotation functions take effect only when the XML attribute value is a Databinding expression (i.e. @ {})

expression

This refers to the expressions used in XML files (used to assign variables), @{} which can write expressions in addition to executing methods, and support some unique expressions

  • Arithmetic + - / *%
  • String merge+
  • Logical & &||
  • Binary & |^
  • One yuan + -~
  • Shift > > > > ><<
  • Compare = = > < > =<=
  • Instanceof
  • Grouping ()
  • Text - character, String, numeric, null
  • Cast
  • Method call
  • Field access
  • Array access []
  • Ternary?:

Avoid null pointers

The null pointer exception will not occur even if the value of variable is set to null or not set

This is because the official has rewritten many properties with the @ BindingAdapter annotation of DataBinding In addition, it was judged as empty

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

.....

android:text="@{userName}"

Null pointer exceptions do not occur

dataBinding.setUserName(null);

It also supports unique non empty multivariate expressions

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

Is equivalent to

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

You still need to pay attention to the fact that the array is out of bounds

aggregate

Collection does not belong to Java Lang *, the full path needs to be imported

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

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

The above writing * reports an error

Error:And element type "variable" Associated "type" Property value cannot contain '<' Character.

Because the < symbol needs to be escaped

Common escape characters

Space;  ; < less than sign <; <; \>Greater than sign >; >; &And &; &; "Quotation marks"; "; 'apostrophe & apos;'; × multiplication sign ×; ×; ÷ division number ÷; ÷;

Correct writing

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

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

Both sets and arrays can use [] to get elements

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

character string

If you want to use strings in @{}, you can use three ways

First:

android:text='@{"Wu Yanzu"}'

Second:

android:text="@{`Wu Yanzu`}"

Third:

android:text="@{@string/user_name}"

It also supports @ color or @ drawable

format string

First define < string > in strings

<string name="string_format">name: %s  Gender: %s</string>

You can then use the DataBinding expression

android:text="@{@string/string_format(`Wu Yanzu`, `male`)}"

Output content:

name: Wu Yanzu gender: male

Default value

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

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

context

DataBinding itself provides a Variable named context It can be used directly Equivalent to getContext() of View

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

Reference 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}"
          />

Reference contains_ The control id of can be ignored directly For example, tv_name writes tvName directly

Thank you lambda for pointing out the error

It can be referenced regardless of order

Use Class

If you want to pass Class as a parameter, the Class cannot be used directly through static import It needs to be used as a field constant

Function callback

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

object

That is, objects are directly used in XML in the same way as attributes This requires you to manually create an object A little trouble

Higher order function

Create custom attributes

object EventDataBindingComponent {

    /**
     * The Model can be used to handle the UI when Binding views. It is not recommended to use the rules that break the view and logical decoupling
     * This can result in inconvenient unit testing of business logic
     *
     * @see OnBindViewListener This interface supports generic definitions and concrete views
     *
     * @receiver View
     * @param block OnBindViewListener<View>
     */
    @JvmStatic
    @BindingAdapter("view")
    fun View.setView(listener: OnBindViewListener) {
        listener.onBind(this)
    }
}

Interface used above

interface OnBindViewListener {
    fun onBind(v: View)
}

Higher order function

<?xml version="1.0" encoding="utf-8"?>
<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="Designer Wu Yanzu"
                  android:onClick="@{v::click}"/>

    </LinearLayout>
</layout>

Using higher-order functions in XML requires matching the following rules

  1. The function parameter of BindingAdapter requires an interface, and Kotlin's function type parameter is not supported
  2. Interface allows only one function
  3. The method signature (return value | parameter) of the interface matches the passed higher-order function

Lambda

Higher order functions do not allow custom transfer parameters (otherwise the interface needs to be modified) Therefore, Lambda can be used for control

Create a multi parameter function

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

XML usage

view="@{(view) -> v.onBinding(view, `Wu Yanzu`)}"

If no parameters are used

view="@{() -> v.onBinding(`Wu Yanzu`)}

ViewDataBinding

Automatically generated DataBinding classes inherit from this class So they all have methods of this class

void    addOnRebindCallback(OnRebindCallback listener)
// Add a binding listener to call back when the Variable is set

void    removeOnRebindCallback(OnRebindCallback listener)
// Delete bound listener

View    getRoot()
// Returns the bound view object
  
abstract void    invalidateAll()
// Invalidate all expressions and reset the expression immediately The OnRebindCallback callback will be triggered again (which can be regarded as a reset)

abstract boolean    setVariable(int variableId, Object value)
// Variables can be set according to the field id

void    unbind()
// Unbound, the ui will not change according to the data, but the listener will trigger

Here are three methods to focus on:

abstract boolean    hasPendingBindings()
// When the ui needs to change according to the current data, it will return true (there is a moment after the data changes)

void    executePendingBindings()
// Force the ui to refresh the data immediately, 

When you change the data (when you set the Observable viewer), the UI will be refreshed immediately, but the UI will not be refreshed until the next frame, with a certain delay During this time, hasPendingBindings() will return true If you want to refresh the UI synchronously (or immediately), you can call executePendingBindings()

OnRebindCallback

The listener can listen to the lifecycle of the layout binding

    mDataBinding.addOnRebindCallback(new OnRebindCallback() {
      /**
       * Before binding
       * @param binding
       * @return If true is returned, the layout will be bound. If false is returned, the layout will be unbound
       */
      @Override public boolean onPreBind(ViewDataBinding binding) {
        return false;
      }

      /**
       * If unbound, the method is called back (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);
      }
    });

OnPropertyChangedCallback

DataBinding also has a data change listener, which can listen to the setting events of variables

mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

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

        break;
    }
  }
});

DataBindingUtil

DataBinding can bind not only activities but also view contents

// 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) // assembly

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)

There are also two less commonly used methods to retrieve whether the view is bound. If not, nul is returned

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

// Different from getBinding, if the view is not bound, it will check whether the parent container is bound
static <T extends ViewDataBinding> T    findBinding(View view)

Other methods

// Return the string type according to the id of BR passed May be used for log output
static String    convertBrIdToString(int id)

For example, Br The name field corresponds to 4. You can use this method to convert 4 to "name"

DataBindingComponent

By default, the BindingAdapter annotation can be used for all XML attributes These custom properties can be switched by making different databindingcomponents

To create a DatabindingComponent:

  1. Create a custom class. The class contains functions that use @ BindingAdapter. No static functions are required At this time, Android studio will automatically generate the databindingcomposer interface
  2. When creating a derived class of DatabindingComponent, you will be prompted that a method requires override If you omit the first step, there will be no
  3. Use the DataBindingUtils tool to set your custom derived classes into Databinding, which includes global defaults and singletons

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():String{

        return text.toString()
    }
}

Step 2

class CusComponent : DataBindingComponent {

    override fun getPinkComponent(): PinkComponent {
        return PinkComponent() // null cannot be returned here
    }
}

Step 3

The default components are set by DataBindingUtils, but the methods are different

static void    setDefaultComponent(DataBindingComponent bindingComponent)

static DataBindingComponent    getDefaultComponent()

The above settings must be set before binding the view. They are global by default and only need to be set once

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

If you do not execute setDefaultComponent, you can choose to pass it in separately through the function. You must pass it in every time, otherwise an error will be reported

DatabindingComponent can only use @ BindingAdapter annotation

be careful

  1. You can use include, but not as the root layout merge cannot be used
  2. If the DataBinding class is not generated automatically, you can write a variable first (or under make module)
  3. Even if you do not bind the data (you may bind the data in the successful network request), the data will be bound as long as the view is created At this time, the data is an empty object The fields of empty objects will also have default values (the default value of String is null, and textview will display NULL); And if you use ternary expressions, the ternary expressions of empty objects are false; Therefore, it is recommended not to consider the case of empty objects;
  4. If you assign a function to a user-defined attribute (BindingAdapter) that requires a Boolean value, the null pointer will return false;

Recommended plug-ins

DataBindingSupport

Automatically create expressions and nodes in XML layout through shortcut keys (alt + enter), and AS4 is invalid

DataBindingConvert

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

Related tutorials

Android Foundation Series tutorials:

Android foundation course U-summary_ Beep beep beep_ bilibili

Android foundation course UI layout_ Beep beep beep_ bilibili

Android basic course UI control_ Beep beep beep_ bilibili

Android foundation course UI animation_ Beep beep beep_ bilibili

Android basic course - use of activity_ Beep beep beep_ bilibili

Android basic course - Fragment usage_ Beep beep beep_ bilibili

Android basic course - Principles of hot repair / hot update technology_ Beep beep beep_ bilibili

This article is transferred from https://juejin.cn/post/6844903549223059463 , in case of infringement, please contact to delete.

Added by rash on Thu, 30 Dec 2021 06:20:12 +0200