Detailed explanation of DataBinding of Android Jetpack component

The original text was first published on WeChat public account: Jongxingzhi (jzman-blog)

The previous summary of the use of ViewModel, LiveData and Lifecycle architecture components, you can read the following article to learn more:

This article mainly focuses on the basic use of dataBinding, the main contents are as follows:

  1. dataBinding support
  2. Layout file configuration
  3. Bind data
  4. Special expression
  5. Event binding
  6. Custom binding class
  7. Others

dataBinding support

To use dataBinding, you need to configure it in the build.gradle file under the app module, as follows:

//设置支持dataBinding   
dataBinding {
    enabled = true
}

Layout file configuration

The Data Binding Library will automatically generate the classes required to bind the views and data objects in the layout. In the layout file of the Data Binding Library, the layout tag is the root tag, and then the specific data elements and view elements. This view element is the binding Determine the location of the layout file, the layout file reference is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!--dataBinding必须以layout作为根标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--数据对象-->
    <data>
        <variable name="user" type="com.manu.databindsample.data.User"/>
    </data>
    <!--视图元素-->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--dataBinding中具体属性值的配置在"@{}"中进行配置-->
        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=姓名}"/>
    </LinearLayout>
</layout>

Data entity

The name attribute in "@{user.name}" is finally mapped to call the getter method of the data object, that is, the getter method. Of course, if there is a corresponding name method in the data object, it will be called when there is no corresponding getter method. For the method with the same name, if both exist, the corresponding getter method will be called first. Refer to the following:

/**
 * 数据实体
 * Powered by jzman.
 * Created on 2018/11/28 0028.
 */
public class User {
    
    
    private String name;

    public User() {
    
    
    }

    public User(String name) {
    
    
        this.name = name;
    }
    
    //两者存在优先调用
    public String getName() {
    
    
        return name;
    }
    //getter方法不存在会调用
    public String name() {
    
    
        return "name";
    }

    //...
}

Bind data

dataBinding will generate the corresponding binding class for the inner layout file. By default, the name of the class is based on the name of the layout file. For example, the layout file name is activity_main, then the binding class corresponding to the layout file is ActivityMainBinding, which contains data All bindings of objects to layout files, then how to bind data and views, the binding method in Activty is as follows:

public class MainActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //生成绑定类
        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        //绑定视图与数据
        User user = new User("张三");
        binding.setUser(user);
    }
}

The binding method in Fragment is as follows:

//inflate方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    
    
    FragmentOneBinding oneBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_one,container,false);
    User user = new User("小明");
    oneBinding.setUser(user);
    return oneBinding.getRoot();
}
//bind方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
    View view = inflater.inflate(R.layout.fragment_one,container,false);
    FragmentOneBinding oneBinding = FragmentOneBinding.bind(view);
    User user = new User("小明");
    oneBinding.setUser(user);
    return view;
}

The binding methods of other layouts are basically done by using the inflate method and bind method of a generated binding class.

Special expression

  • Simplified ternary operator
//完整写法
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
//简写
android:text="@{user.displayName ?? user.lastName}"
  • Null pointer exception handling

The generated binding class will automatically check the null value to avoid NullPointerException. In the expression @ {user.name}, if user is null, user.name is assigned its default value of null. If you refer to user.age, where the type of age is int, the data binding uses the default value of 0.

  • set
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="java.util.Map" />

        <import type="java.util.List" />

        <!--Map-->
        <variable
            name="map"
            type="Map&lt;String,String>" />

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

        <!--List-->
        <variable
            name="list"
            type="List&lt;String>" />

        <variable
            name="index"
            type="int" />
    </data>
    <!--注意Map和List取值方式-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{map.key}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{list[0]}" />

    </LinearLayout>
</layout>

For Map type data, you can use map.key in the expression @{} to get the value value corresponding to the key in the Map collection. List type data directly uses the index to get the value. In addition, the <to be used in the variable tag Escaping, and use <instead of <, otherwise an error will be reported as follows:

> Error: 与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。
  • @{} Use strings in expressions

How to use strings instead of string variables in @{} expressions, there are two ways, as follows:

<!--使用单引号-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{map["key"]}' />
<!--使用后引号-->
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{map[`key`]}" />
<!--在@{}中可以使用字符串资源-->
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{@string/app_name}"/>

Event binding

When using databinding, you can use method reference or listener binding to set up event listeners. The difference between the two is that the event listener of the former is created when the data is bound, and the latter is bound when the event is triggered.

  • Method reference

The event can be directly bound to the event processing method. Compared with the normal android:onClick attribute, this configuration method will be processed during compilation. If the method does not exist or the method signature is incorrect, you will receive the compilation Time error. First create an event handling method as follows:

/**
 * Powered by jzman.
 * Created on 2018/11/30 0030.
 */
public class MyHandler {
    
    
    /**
     * @param view
     */
    public void onClickEvent(View view){
    
    
        Toast.makeText(view.getContext(), "click me", Toast.LENGTH_SHORT).show();
    }
}

Then, configure specific onClick and other events in the corresponding layout file. Here, take the onClick event as an example, as follows:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="handler"
            type="com.manu.databindsample.handler.MyHandler"/>
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--第一种方式-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler::onClickEvent}"/>
        <!--第二种方式-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="click me"
            android:onClick="@{handler.onClickEvent}"/>
    </LinearLayout>
</layout>

Finally, just set the data object handler in the corresponding Activity, the specific reference is as follows:

@Override
protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setHandler(new MyHandler());
}

In this way, the event referenced by the method is successfully bound.

  • Monitor binding

This method is to create an event listener when an event occurs. Compared with a method reference, you can pass custom parameters in the event callback. First, create an event callback method as follows:

/**
 * 监听绑定
 * Powered by jzman.
 * Created on 2018/12/3 0003.
 */
public class MyPresenter {
    
    
    private Context mContext;

    public MyPresenter(Context mContext) {
    
    
        this.mContext = mContext;
    }

    public void onClickEvent(User user) {
    
    
        Toast.makeText(mContext, user.getName(), Toast.LENGTH_SHORT).show();
    }
}

Then, configure specific onClick and other events in the corresponding layout file. Here, take the onClick event as an example, as follows:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.manu.databindsample.data.User" />

        <variable
            name="presenter"
            type="com.manu.databindsample.handler.MyPresenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onClickEvent(user)}"
            android:text="click me 3" />
    </LinearLayout>
</layout>

Finally, just set the data object presenter in the corresponding Activity, the specific reference is as follows:

@Override
protected void onCreate(Bundle savedInstanceState) {
    
    
    super.onCreate(savedInstanceState);
    ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
    binding.setUser(new User("android"));
    binding.setPresenter(new MyPresenter(this));
}

In this way, the event is successfully bound through event monitoring. When the event method is called in the above xml, the current View can be configured as follows:

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{(view) -> presenter.onClickEvent(user)}"
    android:text="click me 3" />

The corresponding event callback method is as follows:

public class MyPresenter {
    
    
    public void onClickEvent(View view, User user){
    
    }
}

In addition, you can also use the ternary operator when binding events. In this case, you can use void as an operator. Refer to the following for the usage:

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

Custom binding class

As you can see from the foregoing, the default binding class name is determined by the layout file name, so how to customize the binding class? Use the class attribute on the layout file data tag to specify the custom binding class name, of course Add the completed package path in front of the custom class name, refer to the following:

<!--自定义绑定类-->
<data class="com.manu.test.CustomBinding">
    <variable name="user" type="com.manu.databindsample.data.User"/>
</data>

Others

Use the import keyword in databinding to import related classes. The related classes below java.lang.* are automatically imported by default. If there are Views with the same name, you can use alias to distinguish them. Refer to the following:

<import type="android.view.View"/>
<import type="com.manu.View"
        alias="MView"/>

Use the variable keyword to define the variables to be used in the xml layout. If the include layout is used, use bind to bind the layout included in the include and the main layout using the same variables, and create a layout test_layout.xml file that includes the include. as follows:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="userTest"
            type="com.manu.databindsample.data.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`this is include content...`+userTest.getName(),default=user}" />
    </LinearLayout>
</layout>

Then, reference this layout in the main layout, as follows:

<?xml version="1.0" encoding="utf-8"?>

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

    <data>
        <variable
            name="user"
            type="com.manu.databindsample.data.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
         <!--bind绑定变量-->
        <include
            layout="@layout/test_layout"
            bind:userTest="@{user}" />
    </LinearLayout>
</layout>

In this way, the variables of the User type used in the two layouts are bound by bind, so that the variables used in the two layouts are the same variable. In addition, the databinding does not support the merge tag. The next article continues the introduction of Binding adapters.

Insert picture description here

Guess you like

Origin blog.csdn.net/jzman/article/details/106325231