Android DataBinding从入门到精通

DataBinding可以更加方便的编写与视图交互的代码。即系统会为模块中的每个xml文件生成一个绑定类,其实例包含指向相应布局中具有ID的所有视图的直接引用。大多数情况下,DataBinding会代替findMyId。

启动DataBinding

在Android SDK 32及后续版本中(PCCT控制面板用的版本为SDK 34),在项目gradle中配置:

Java 复制代码
adroid {
    compileSdk 32
    buildFeatures {
        viewBinding true
    }
}

然后,系统会将XML(layout)文件的名称转换为驼峰式,并在末尾添加"Binding"作为词缀。即若xml的文件名称为ActDataBindg1,那么会自动生成ActDataBinding1Binding类,可以直接使用。

Java调用

在DataBindingAct1中,需要调用setContentView

Java 复制代码
public class DataBindingAct1 extends AbsActivity {
    ActDataBinding1Binding binding;
    
    @Override
    protected void onCreate(Bundle savedInstantceState){
        super.onCreate(savedInstanceState);
        binding = ActDataBinding1Binding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        
        binding.tv1.setText("可以直接使用TextView");
        binding.tv2.setText("不用findViewById");
    }
}

可以看出,不像过去的方式,不用写那么多冗余的findViewById,可以直接使用binding。同时,每个类还绑定一个getRoot()方法用于为相应布局文件的根视图提供引用。

DataBindingUtil

在上文的ActDataBinding1Binding.inflate(getLaoutInflater())也可以用于换为DataBindingUtil.setContentView,如下所示:

Java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    binding = DataBindingUtil.setContentView(this, R.layout.act_data_binding_1);
}

Binding与findViewById的区别:

与使用findViewById相比,将视图绑定有如下优点:

  • Null安全:由于视图绑定创建了对视图的直接引用,因此不会出现因视图无效ID而出现Null异常。同时,若视图只出现在布局的某些配置中,那么绑定类中的引用字段会加@Nullable注解。
  • 类型安全:每个绑定类中的字段均有与之匹配的类型(XML文件中引用的视图)。这意味着不会发生类转换异常的问题。

Activity示例

首先提供一个抽象类BaseAct封装一些常用操作,继承ViewDataBinding

Java 复制代码
/**
 * 抽象类
 */
 public abstract class BaseAct<B extends ViewDataBinding> extends AppCompatActivity {
     protected B binding;
     // @return 界面对应的layout id
     protected abstract int getLayoutId();
     
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(saveInstanceState);
         binding = DataBindingUtil.setContentView(this, getLayoutId());
     }
 }

实现类继承BaseAct,指定ViewDataBinding的子类后,就可以使用binding对象了。

Java 复制代码
public class MainActivity extends BaseAct<ActMainBinding> {
    @Override
    protected int getLayoutId() {
        return R.layout.act_main;
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // binding操作
    }
    
    @Override
    public void onBackPressed() {
    
    }
}

使用Observable

DataBinding提供Obeservable接口用于监听实体类对象属性的变化,其具有添加、删除监听的功能。

我们可以直接实现基本监听类BaseObeservable,然后在get方法加上@Bindable注解,set方法中直接调用notifyPropertyChanged通知UI更新即可。

继承BaseObservable类

设计一个类SysInfo(代表业务上的数据),并让它集成BaseObservable类,给Get方法加@Bindable注解

Java 复制代码
public class SysInfo {
    private String info1 = build.MANUFACTURER;
    private String timeStr = " ";
    private long time;
    
    @Bindable
    public String getInfo1() 
        return info1;
    }
    
    public void setTimeStr(String timeStr) {
        this.timeStr = timeStr;
        notifyPropertyChanged(BR.timeStr);
    }
    
    @Bindable
    public long getTime() {
        return time;
    }
    
    public void setTime(long time) {
        this.time = time;
        notifyPropertyChanged(BR.time);
    }
}

此时,SysInfo类就是可以被观测到的了。其中在set方法中调用notifyPropertyChanged()、get方法上加@Bindable注解,在编译工程后,DataBinding就会在BR文件中生成相应字段。【BR是编译期间生成的类,相当于R文件】

接下来,定义工具类DataUtils的,提供静态方法【后续会在layout中用到】

Java 复制代码
public class DataUtils {
    private static SimpleDateFormat format =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
    public static String formatTime(long time) {
        return format.format(time);
    }
}

layout布局

在data标签下使用多个import标签把使用的类导入。

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

    <data>
        // 引入[import]工具类DataUtils
        <import type="com.rustfisher.tutorial2020.databinding.DataUtils" />
        // 生命[variable]使用SysInfo对象。变量名为info
        <variable
            name="info"
            type="com.rustfisher.tutorial2020.databinding.data.SysInfo" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">
        // 使用DataUtils方法,也是@{}
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{info.info1}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{info.timeStr}" />
        // 在使用对象的属性时,也可以直接进行操作
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{DataUtils.formatTime(info.time)}" />
    </LinearLayout>
</layout>

Activity代码

在layout文件中设置了SysInfo变量后,binding会自动生成binding.setInfo()方法。

在Activity中创建一个SysInfo对象,交由binding。后续这个对象的数据变化时,界面即可相应改变。为演示数据变化,使用定时器更新数据。

Java 复制代码
public class DataBindingAct1 extends AbsActivity {
    private ActDataBinding1Binding binding;
    private SysInfo mSysInfo = new SysInfo();
    private Timer mTimer;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        binding = DataBindingUtil.setContentView(this, R.layout.act_data_binding_1);
        binding.setInfo(mSysInfo);
        
        mTimer = new Timer();
        // 定期执行,每500毫秒执行一次[0 表示0延迟,500 表示周期时间]
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                mSysInfo.setTimeStr("Time: " + System.currentTimeMillis());
                mSysInfo.setTime(System.currentTimeMillis());
            }
        }, 0, 500);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mTimer.cancel();
    }
}

可观察数据对象ObservableField

对于基础类型使用如下类型即可:

Java 复制代码
ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable

对于自创建对象,使用可观察字段避免访问操作期间封箱和开箱的操作,使用此机制需要使用public final属性:

Java 复制代码
User.java
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

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

// 要访问字段值,使用set()和get()方法
user.firstName.set("Google");
int age = user.age.get();

使用ObservableField

在使用Observable时,在每个set方法都需要调用notifyPropertyChanged去通知ui,而在ObservableField即可避免这个事情:

Java 复制代码
public class SysInfoObs {
    public ObservableField<String> info1 = new ObservableField<>(Build.MANUFACTURER);
    public ObservableField<String> timeStr = new ObservableField<>();
    public ObservableField<Long> time = new ObservableField<>();
}

layout与上文大致相同,只需将variable改为新建的SysInfoObs类:

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

    <data>

        <import type="com.rustfisher.tutorial2020.databinding.DataUtils" />
        // 将variable改为新建的SysInfoObs类
        <variable
            name="info"
            type="com.rustfisher.tutorial2020.databinding.data.SysInfoObs" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

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

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{DataUtils.formatTime(info.time)}" />
    </LinearLayout>
</layout>

在Activity中更改使用方式,新建DataBindingAct2类,持有SysInfoObs的对象:

Java 复制代码
public class DataBindingAct2 extends AbsActivity {
    private ActDataBinding2Binding binding;
    private SysInfoObs mSysInfo = new SysInfoObs();
    
    private Timer mTimer;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        binding = DataBindingUtil.setContenteView(this, R.layout.act_data_binding_2);
        binidng.setInfo(mSysInfo);
        
        mTimer = new Timer();
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                mSysInfo.timeStr.set("Time: " + System.currentTimeMillis());
                mSysInfo.time.set(System.currentTimeMillis());
            }
        }, 0, 500);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mTimer.cancel();
    }
}

在更新数据时,使用ObservableField的set方法即可。

可观察集合

某些应用使用动态结构存储数据,当键为引用时,ObservableArrayMap则非常有用:

Java 复制代码
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "RUST");
user.put("lastName", "Fisher");
user.put("age", 18);
binding.setUser(user);

layout文件如下:

Java 复制代码
<data>
    <import type="androidx.databinding.ObservableArrayMap" />
    // 注意Map的左尖括号要使用&lt
    <variable
        name="user"
        type="ObservableArrayMap&lt;String, Object>" />
</data>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text='@{user.firstName + " " + user.lastName}' />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(1 + (Integer)user.age)}" />

使用ObservableArraylist如下所示:

Java 复制代码
ObservableArrayList<Object> obList = new ObservableArrayList<>();
obList.add("NeoSoft");
obList.add("Java");
obList.add("Android");
obList.add(2020);
binding.setObList(obList);

在layout中设置

Java 复制代码
<data>
    <import type="androidx.databinding.ObservableArrayList" />

    <variable
        name="obList"
        type="ObservableArrayList&lt;Object>" />
</data>

<!-- .... -->

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{obList[0] + " " + obList[1]}' />

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{String.valueOf(1 + (Integer)obList[3])}' />

点击事件

准备监听方法,以MutableDemoVm类为例

Java 复制代码
public class MutableDemoVM {
    // ... 
    // 用于设置点击监听
    public void onClickBack(View view) {
        // ...
    }
    // 用于设置点击监听
    public void onClickAdd(View view) {
        // ...
    }
}

其中需要View作为参数,因为要对应View.OnClickListener的onClick(View v)方法

Java 复制代码
public interface OnClickListener {
    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    void onClick(View v);
}

layout中设置监听方法

设置android:onClick监听

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

    <data>
        <variable
            name="vm"
            type="com.rustfisher.tutorial2020.databinding.data.MutableDemoVM" />
    </data>

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

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="50dp"
                android:layout_height="50dp"
                android:onClick="@{vm.onClickBack}"
                android:padding="10dp"
                android:src="@drawable/ic_arrow_back_black_24dp" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            // 设置android:onClick时,建议用2个冒号@{vm::onClickAdd}来引用方法都可以
            // 随着Jetpack的升级,后面只用2个冒号的形式
            <Button
                android:layout_width="100dp"
                android:layout_height="50dp"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="20dp"
                android:onClick="@{vm::onClickAdd}"
                android:text="+" />
        </LinearLayout>

    </LinearLayout>
</layout>

在activity中设置数据

Java 复制代码
public class MutableDemo1 extends AppCompatActivity {
    private ActMutableDemo1Binding binding;
    private MutableDemoVM mVM = new MutableDemoVm();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        binding = DataBindingUtil.setContentView(this, R.layout.act_mutable_demo1);
        binding.setVm(mVM);
        // ...
    }
}

RadioButton OnCheckedChangeListener

在类中定义好选择的方法,方法签名和CompoundButton.OnCheckedChangeListener.onCheckedChanged(CompoundButton buttonView, boolean isChecked)一致。

Java 复制代码
public void choose1(CompoundButton buttonView, boolean isChecked) {
    // ...
}

public void choose2(CompoundButton buttonView, boolean isChecked) {
    // ...
}

在layout中设置使用方法。

Java 复制代码
<RadioGroup
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onCheckedChanged="@{viewModel.choose1}"
        android:text="选项1"/>

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onCheckedChanged="@{viewModel.choose2}"
        android:text="选项2" />
</RadioGroup>
相关推荐
阿巴斯甜15 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker15 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952716 小时前
Andorid Google 登录接入文档
android
黄林晴18 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android