One article on databinding is enough

Use articles

First find the app's build.gradle:

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdk 24
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    //Add the following configuration
    dataBinding {
        enabled = true
    }

}

Then enter the xml file and convert our xml into a databinding layout:

<?xml version="1.0" encoding="utf-8"?>
<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>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Instructions about the IDE's automatic conversion to databinding layout: Stop the mouse at the free position in the layout of the outermost constraintLayout, and then press option+enter (Mac) at the same time, and the convert to data binding layout prompt will appear.

Let's start by writing a bean:

package com.example.myapplication

import androidx.annotation.Keep

@Keep
data class User(
    var userName: String,
    var passWord: String
)

Then the layout is written like this:

<?xml version="1.0" encoding="utf-8"?>
<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="user"
            type="com.example.myapplication.User" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.userName}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

name is the name, you can write it casually, it is generally best to be consistent with the bean, and type is the specifically defined type. @{user.***} implements data reference.

MainActivity uses:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.myapplication.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    var activityMainBinding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        val user = User("lzy", "123456")
        activityMainBinding?.user = user
    }
}

Note that the original way of setting the layout should be replaced by the way of DataBindingUtil.

Run a handful:

This completes the use of the most basic databinding. Then some students may say, this is very troublesome, what are the advantages of using databinding? Let's see next:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.myapplication.databinding.ActivityMainBinding
import java.lang.Thread.sleep

class MainActivity : AppCompatActivity() {

    var activityMainBinding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        val user = User("lzy", "123456")
        activityMainBinding?.user = user

        Thread {
            for (i in 0 until 100) {
                sleep(1000)
                user.userName = "lzy".plus(i)
                activityMainBinding?.user = user
            }
        }.start()
    }
}

The effect we see is this:

 Every second, the UI will automatically change. It is not obvious that we only have one TextView. If we have many Views, when the data class changes, it will automatically drive the UI changes, which will make the code more concise. You need to set data for each View.

Going a step further, we can also define a Bean class like this:

package com.example.myapplication;

import androidx.annotation.Keep;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

@Keep
public class User2 extends BaseObservable {

    private String userName;
    private String passWord;

    @Bindable
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
        notifyPropertyChanged(BR.userName);
    }

    @Bindable
    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
        notifyPropertyChanged(BR.passWord);
    }

}

use:

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.myapplication.databinding.ActivityMainBinding
import java.lang.Thread.sleep

class MainActivity : AppCompatActivity() {

    private var activityMainBinding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        //val user = User("lzy", "123456")
        val user = User2()
        user.userName = "lzy2"
        user.passWord = "123456789"
        activityMainBinding?.user = user

        Thread {
            for (i in 0 until 100) {
                sleep(1000)
                user.userName = "lzy".plus(i)
            }
        }.start()
    }
}

We found that after User2 inherits BaseObservable and uses notifyPropertyChanged, data changes automatically trigger UI changes.

Principles

Like other Jetpack components, the use of databinding is as simple as ever, but it's not enough to just use it, we must master the principles.

We first focus on the XML file. The XML file we write will be automatically converted into two other files through databinding:

File 1:

app/build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app/src/main/res/layout/activity_main.xml"
    isBindingData="true" isMerge="false" layout="activity_main"
    modulePackage="com.example.myapplication" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
    <Variables name="user" declared="true" type="com.example.myapplication.User2">
        <location endLine="9" endOffset="52" startLine="7" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_main_0"
            view="androidx.constraintlayout.widget.ConstraintLayout">
            <Expressions />
            <location endLine="27" endOffset="55" startLine="13" startOffset="4" />
        </Target>
        <Target tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.userName">
                    <Location endLine="21" endOffset="42" startLine="21" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="21" endOffset="40" startLine="21" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="25" endOffset="55" startLine="18" startOffset="8" />
        </Target>
    </Targets>
</Layout>

Note: There are two Target elements in Targets, which will be used later.

File 2:

app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:tag="layout/activity_main_0"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:tag="binding_1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Let's see what exactly does setContentView do?

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);//1
    }

We focus on note 1:

private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        final int endChildren = parent.getChildCount();
        final int childrenAdded = endChildren - startChildren;
        if (childrenAdded == 1) {
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            final View[] children = new View[childrenAdded];
            for (int i = 0; i < childrenAdded; i++) {
                children[i] = parent.getChildAt(i + startChildren);
            }
            return bind(component, children, layoutId);//1
        }
    }

Let's look at the bind method:

static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

What is sMapper?

To be continued!

Tags: Android jetpack

Posted by Stegs on Fri, 02 Sep 2022 01:04:21 +0530