Skip to content

使用可观察数据对象ObservableField

更新日期 2021-9-1
  • 2021-9-1 完善Kotlin内容
  • 2021-6-26 创建文档

可观察性是指一个对象将其数据变化通知给其他对象的能力。通过数据绑定库,您可以让对象、字段或集合变为可观察。 任何 plain-old 对象都可用于数据绑定,但修改对象不会自动使界面更新。通过数据绑定,数据对象可在其数据发生更改时通知其他对象,即监听器。可观察类有三种不同类型:对象、字段和集合。

当其中一个可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新。

可观察字段

在创建实现 Observable 接口的类时要完成一些操作,但如果您的类只有少数几个属性,则这样操作的意义不大。在这种情况下,您可以使用通用 Observable 类和以下特定于基元的类,将字段设为可观察字段:

可观察字段
  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

可观察字段是具有单个字段的自包含可观察对象。 避免在访问操作期间封箱和开箱。要使用此机制,Java中使用 public final 属性,或在 Kotlin 中创建只读属性,如以下示例所示:

User类

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();

import androidx.databinding.ObservableField
import androidx.databinding.ObservableInt

class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

使用ObservableField

使用Observable的时候,我们在每个set方法里,都调用notifyPropertyChanged去通知ui。 我们也可以换用ObservableField来实现动态更新。

新建类SysInfoObs,里面使用ObservableField<T>。可以定义多个属性。

新建类

SysInfoObs.java

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

目前一个工程里同时有Java和Kotlin版本的示例代码,所以给它们起了不同的类名 SysInfoObsKt.kt

import android.os.Build
import androidx.databinding.ObservableField

class SysInfoObsKt {
    val info1 = ObservableField(Build.MANUFACTURER)
    var timeStr = ObservableField<String>()
    var time = ObservableField<Long>()
}

layout和之前的差不多。引入的variable改为我们新建的SysInfoObs类。

layout引入variable

variable里使用SysInfoObs,部分内容。

<?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
            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>

使用SysInfoObsKt的xml。部分内容。

<?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
            name="info"
            type="com.rustfisher.tutorial2020.databinding.data.SysInfoObsKt" />
    </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的对象。

activity

DataBindingAct2.java

import android.os.Bundle;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ObservableArrayList;
import androidx.databinding.ObservableArrayMap;
import com.rustfisher.tutorial2020.AbsActivity;
import com.rustfisher.tutorial2020.R;
import com.rustfisher.tutorial2020.databinding.data.SysInfoObs;
import java.util.Timer;
import java.util.TimerTask;

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.setContentView(this, R.layout.act_data_binding_2);
        binding.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();
    }
}

这里是完整的DataBindingAct2Kt代码

import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableArrayList
import androidx.databinding.ObservableArrayMap
import com.rustfisher.tutorial2020.AbsActivity
import com.rustfisher.tutorial2020.R
import com.rustfisher.tutorial2020.databinding.data.SysInfoObsKt
import java.util.*

/**
* 演示ObservableFile的kt版本
*/
class DataBindingAct2Kt : AbsActivity() {
    private val mSysInfo = SysInfoObsKt()
    private var mTimer: Timer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActDataBinding2KtBinding =
                DataBindingUtil.setContentView(this, R.layout.act_data_binding_2_kt)
        binding.info = mSysInfo
        mTimer = Timer()
        mTimer!!.schedule(object : TimerTask() {
            override fun run() {
                mSysInfo.timeStr.set("Time: " + System.currentTimeMillis())
                mSysInfo.time.set(System.currentTimeMillis())
            }
        }, 0, 100)
        val user = ObservableArrayMap<String, Any>()
        user["firstName"] = "Rust"
        user["lastName"] = "Fisher"
        user["age"] = 20
        binding.user = user
        val obList = ObservableArrayList<Any>()
        obList.add("Rust")
        obList.add("Fisher")
        obList.add("Android")
        obList.add(2020)
        obList.add("Kotlin")
        binding.obList = obList
    }

    override fun onDestroy() {
        super.onDestroy()
        mTimer!!.cancel()
    }
}

要更新数据时,需要调用ObservableFieldset方法。 运行起来就可以看到更新UI的效果了。

可观察集合

某些应用使用动态结构来保存数据。可观察集合允许使用键访问这些结构。 当键为引用类型(如 String)时,ObservableArrayMap 类非常有用,如以下示例所示:

ObservableArrayMap的使用
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Rust");
user.put("lastName", "Fisher");
user.put("age", 18);
binding.setUser(user);
val user = ObservableArrayMap<String, Any>()
user["firstName"] = "Rust"
user["lastName"] = "Fisher"
user["age"] = 20
binding.user = user

在布局中,可使用字符串键找到Map,如下所示:

<data>
    <import type="androidx.databinding.ObservableArrayMap" />
    <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)}" />
注意Map的左尖括号要改写为&lt;

使用 ObservableArrayList 类,如下所示:

ObservableArrayList
ObservableArrayList<Object> obList = new ObservableArrayList<>();
obList.add("Rust");
obList.add("Fisher");
obList.add("Android");
obList.add(2020);
binding.setObList(obList);
val obList = ObservableArrayList<Any>()
obList.add("Rust")
obList.add("Fisher")
obList.add("Android")
obList.add(2020)
obList.add("Kotlin")
binding.obList = obList

在layout中进行设置

<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])}' />

Kotlin版

我们用Kotlin来实现上面的例子。主要看以下3个文件。代码逻辑和前面的一致。

  • SysInfoObsKt.kt 信息类
  • act_data_binding_2_kt.xml layout
  • DataBindingAct2Kt.kt 界面Activity
SysInfoObsKt.kt
import android.os.Build
import androidx.databinding.ObservableField

class SysInfoObsKt {
    val info1 = ObservableField(Build.MANUFACTURER)
    var timeStr = ObservableField<String>()
    var time = ObservableField<Long>()
}
layout
<?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
            name="info"
            type="com.rustfisher.tutorial2020.databinding.data.SysInfoObsKt" />

        <import type="androidx.databinding.ObservableArrayMap" />

        <variable
            name="user"
            type="ObservableArrayMap&lt;String, Object>" />

        <import type="androidx.databinding.ObservableArrayList" />

        <variable
            name="obList"
            type="ObservableArrayList&lt;Object>" />
    </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="ObservableField kt" />

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

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_margin="8dp"
            android:background="#f0f0f0" />

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

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_margin="8dp"
            android:background="#f0f0f0" />

        <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])}' />

    </LinearLayout>
</layout>
Activity界面代码
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableArrayList
import androidx.databinding.ObservableArrayMap
import com.rustfisher.tutorial2020.AbsActivity
import com.rustfisher.tutorial2020.R
import com.rustfisher.tutorial2020.databinding.data.SysInfoObsKt
import java.util.*

/**
* 演示ObservableFile的kt版本
*/
class DataBindingAct2Kt : AbsActivity() {
    private val mSysInfo = SysInfoObsKt()
    private var mTimer: Timer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActDataBinding2KtBinding =
                DataBindingUtil.setContentView(this, R.layout.act_data_binding_2_kt)
        binding.info = mSysInfo
        mTimer = Timer()
        mTimer!!.schedule(object : TimerTask() {
            override fun run() {
                mSysInfo.timeStr.set("Time: " + System.currentTimeMillis())
                mSysInfo.time.set(System.currentTimeMillis())
            }
        }, 0, 100)
        val user = ObservableArrayMap<String, Any>()
        user["firstName"] = "Rust"
        user["lastName"] = "Fisher"
        user["age"] = 20
        binding.user = user
        val obList = ObservableArrayList<Any>()
        obList.add("Rust")
        obList.add("Fisher")
        obList.add("Android")
        obList.add(2020)
        obList.add("Kotlin")
        binding.obList = obList
    }

    override fun onDestroy() {
        super.onDestroy()
        mTimer!!.cancel()
    }
}

示例代码

作者: RustFisher
联系: rf.cs@foxmail.com
博客: rustfisher.com | RustFisher cnblog
示例: AndroidTutorial Gitee, Tutorial Github
链接: https://www.an.rustfisher.com/android/jetpack/dataBinding/use_observableField/
一家之言,仅当抛砖引玉。如有错漏,还请指出。