在XML中的双向数据绑定,ObservableField和BaseObservable,我应该使用哪一个来实现双向数据绑定?

18

我已经使用数据绑定一段时间了,即使现在JDK 8和API 24还不支持它。但我仍然找到了一种更容易使用数据绑定的方式。但是当我使用以下方式来进行精确的双向数据绑定(在我的想法中,双向数据绑定就像这里所说的(什么是双向绑定?)时,出现了奇怪的情况。

1. 在xml中实现双向数据绑定

android:text="@={testStr}"
官方文档(https://developer.android.com/topic/libraries/data-binding/index.html,此页面通常会更新,可能已经更改)中没有提到这一点。但是可以将变量绑定到xml中。 2. 可观察字段用于属性 示例来自(https://developer.android.com/topic/libraries/data-binding/index.html#observablefields)。
private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

3. 将模型类扩展到 BaseObservable

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

模型类必须扩展到BaseObservable类,并且getter方法必须用"@Bindable"进行注释,setter方法需要在绑定xml中与相应的命名调用“notifyPropertyChanged()”方法。

我的问题是,我想了解三种绑定方法的优缺点。当然,我知道第一种方法更容易。但有时我在文档和一些网站上发现它,然后下一刻它就消失了。官方文档已经改变,没有任何明确的公告。我仍然不确定是否应该使用第一种方法,所以我必须准备好改变为方法2或3。

Student_XML2WAY.java

public class Student_XML2WAY {
    private int age;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int pAge) {
        age = pAge;
    }
    public String getName() {
        return name;
    }
    public void setName(String pName) {
        name = pName;
    }
}

学生观察者字段.java

public class Student_ObserField {
    private ObservableInt age;
    private ObservableField<String> name;
    public Student_ObserField() {
        age = new ObservableInt();
        name = new ObservableField<>();
    }
    public ObservableInt getAge() {
        return age;
    }
    public ObservableField<String> getName() {
        return name;
    }
}

学生扩展.java

public class Student_Extend  extends BaseObservable{
    private int age;
    private String name;

    @Bindable
    public int getAge() {
        return age;
    }
    public void setAge(int pAge) {
        age = pAge;
        notifyPropertyChanged(BR.student3);
    }
    @Bindable
    public String getName() {
        return name;
    }
    public void setName(String pName) {
        name = pName;
        notifyPropertyChanged(BR.student3);
    }
}

主活动界面.xml

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

    <data>

        <variable
            name="student1"
            type="example.com.testerapplication.sp.bean.Student_XML2WAY"/>

        <variable
            name="student2"
            type="example.com.testerapplication.sp.bean.Student_ObserField"/>

        <variable
            name="student3"
            type="example.com.testerapplication.sp.bean.Student_Extend"/>

    </data>

    <LinearLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
      >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={student1.name}"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{student2.name}"/>

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{student3.name}"/>
        <Button
            android:id="@+id/btn1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="update"/>
    </LinearLayout>
</layout>
活动类
public class MainActivity extends AppCompatActivity {
    private Student_XML2WAY mStudent1;
    private Student_ObserField mStudent2;
    private Student_Extend mStudent3;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.activity_main, null, false);
        mStudent1 = new Student_XML2WAY();
        mStudent1.setName("XML First");
        mStudent2 = new Student_ObserField();
        mStudent2.getName().set("ObserField Second");
        mStudent3 = new Student_Extend();
        mStudent3.setName("Extend Third");
        binding.setStudent1(mStudent1);
        binding.setStudent2(mStudent2);
        binding.setStudent3(mStudent3);
        setContentView(binding.getRoot());
        binding.btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mStudent1.setName("Student1");
                mStudent2.getName().set("Student2");
                mStudent3.setName("Student3");
            }
        });
    }
}

由于我并不真正理解你的问题(因为正确实现的双向绑定对我来说很好用),我想建议您阅读StackOverflow文档,那里有一些示例,此外,这个库的开发者George Mount也写了一些博客文章关于这个主题。 - yennsarah
问题已更新。我已经阅读了博客并在我的应用程序中使用了它。但是缺点不清楚,官方文档也没有明确说明绑定方法。其他方法如setVariable、executePendingBindings也提供了绑定功能。这让我感到困惑,我应该使用哪个方法以及如何使用(何时调用它以及在哪里调用它)。 - Long Ranger
你提到的第一种方法并不是另外两种的替代方案,而是声明双向数据绑定的方式。而第二和第三种方法则是可供选择的替代方案,你可以根据时间成本来决定最便宜的方式。根据文档所述,“创建Observable类需要一些工作,因此想要节省时间或只有少量属性的开发人员可以使用ObservableField及其兄弟类ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble和ObservableParcelable。” - Bronx
但是,如果模型中有一个对象(使用方法2)也绑定了一些属性,那么嵌套对象的属性是否也会被更改为Observable类型? - Long Ranger
2个回答

10

由于您的Student_XML2WAY.java没有满足实现双向绑定所需的条件(例如: BaseObservable, Bindable等),因此它无法使用双向绑定。

如果我要直接访问模型,我会使用BaseObservable,就像在您的Student_Extend中一样。我将在我的Activity中拥有一个Student_Extend实例,并在onCreate中设置变量:

Student mStudent = new Student("John Doe", 42); //
binding.setStudent(mStudent);
//later:
mStudent.setAge(37);
如果正确实现,这也将更改您UI中的Age(以及您的模型)。
如果不想直接访问您的模型并想使用ViewModel,我会使用ObservableFields
public class Student {
    private String name;
    private int age;
    //Corresponding setters and getters
}


public class StudentViewModel {
    private ObservableField<Student> mStudentField = new ObservableField<>();

    //if I have a large model class, and only want to use some fields, 
    //I create some getters (and setters, for the two way attributes)
    //Something like this:

    public int getAge() {
        return mStudentField.get().getAge();
    }
    public void setAge(int newAge) {
        return mStudentField.get().setAge(newAge);
    }
}

因此,我在我的Activity中创建了一个StudentViewModel的实例,并将其设置为绑定对象。伪XML示例如下:

<layout>
    <data>
        <variable name="studentViewModel" 
                  type="locaction.of.StudentViewModel"> <!-- or do an import -->
    </data>
    <EditText 
        android:text="@={studentViewModel.age}"/>
</layout>
因此,ViewModel方法更加“清晰”,因为你几乎将所有与视图有关的内容外包。将你的BindingAdapter、点击方法、转换器方法放在那里并保持你的Activity清洁。此外,你不直接改变你的模型。 这种方法对于简单的类和项目可能过于繁琐。;)
如果你想看一个完整的例子,使用DataBinding和MVVM,请查看Droids on roids在这方面的方法。

我已经更新了问题,加入了点击事件。我试图使用更新后的变量测试按钮点击。但只有第二个被更新了。即使我使用了你的View Model建议,学生类的属性如“年龄”、“姓名”也需要改为“Observable”类型吗?我担心这会对中型项目造成灾难性的影响。这不像可轻易删除和生成的Parcelable接口。 - Long Ranger
正如我所说,你的student1是一个POJO,没有DataBinding的知识。第三个应该也会更新,我有点困惑为什么没有。虽然这不是一个好的做法,但你可以检查一下是否在调用binding.executePendingBindings()时值得到了更新。我将添加Student.class以澄清我的ViewModel示例。 - yennsarah
好的,我现在明白方法1了。只有当我重置值时,才能更新变量和UI中的绑定。对于方法3,我尝试过但不起作用。只有当我将“BR.student3”更改为“BR.name”时,它才会起作用。我想知道为什么“BR.name”可以工作,而只有student3的名称(而不是其他名称)被更新。 - Long Ranger
哦!现在我明白了。你需要调用 BR.yourPropertyName,而不是像 student3 这样的 变量名。如果你有一个叫做 age 的属性,那么它应该是 BR.age - yennsarah
感谢您的回复和帮助。MVVM是绑定的好方法。目前,ViewModel和Observable Fields是避免在代码修改中进行大量更改的最佳方法。如果我在模型中使用ORM,扩展Observable会有麻烦。 - Long Ranger

2
我认为使用ObservableField方法是可行的,因为不需要编写getter/setter或调用notifyPropertyChanged。此外,如果您有一个自定义对象ObservableField<Student> studentField,并且您使用android:text="@{viewModel.studentField.name},当您调用studentField.set(newStudent)时,文本会得到更新。
我发现RxJava非常有用。可以轻松地将ObservableField转换为rx.Observable,反之亦然。这允许使用Rx操作符。如果您感兴趣,可以在此处查看实现:FieldUtils.java

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接