是否可以将一个ObservableField绑定到另一个?

时间:2015-08-06 09:12:08

标签: android data-binding

我理解Android's data-binding library的目的是观察数据并在数据发生变化时自动更新。

问题:数据是否可以观察其他数据?例如,我可以拥有一个ObservableField"依赖于"或"绑定到"另一个或一组其他ObservableField的价值?目前,我已经手动实现了这一点 - 每次任何" dependee" ObservableField更改,我计算从属字段并更新其值。

详细信息

我的用例是我想要所有"逻辑"在视图之外 - 所以我想把我所有的逻辑放在"数据" class(ViewModel,如果可以的话)。我有一个按钮,其状态我想设置为启用/禁用,具体取决于其他几个字段的内容。这个例子说明了我的想法。

我的布局文件如下所示

<layout>
    <data>
        <variable name="register" class="com.example.RegisterViewModel"/>
    </data>
    <LinearLayout>
        <EditText 
            android:id="@+id/edUsername" 
            android:text="@{register.username}"/>
        <EditText android:id="@+id/edPassword" />
        <EditText android:id="@+id/edConfirm" />
        <Button android:text="Register" android:enabled="@{register.isValid}" />
    </LinearLayout>
</layout>

而且,我的View代码如下:

class RegisterView extends View {
    @Override
    protected void onFinishInflate() {
        RegisterViewBinding binding = DataBindingUtil.bind(this);
        RegisterViewModel register = new RegisterViewModel();
        binding.setRegister(register);
        binding.edPassword.setOnFocusChangeListener(new OnFocusChangeListener(){
            @Override public void onFocusChange(View v, boolean hasFocus){
                register.updateUsername(edPassword.getText().toString());
            }
        });

        //Similarly for other fields.
    }
}

这是我的ViewModel

class RegisterViewModel {
    public final ObservableField<String> username = new ObservableField<>();
    private final ObservableField<String> password = new ObservableField<>();
    private final ObservableField<String> confirmPassword = new ObservableField<>();
    public final ObservableBoolean isValid;

    //Dependee Observables - isValid depends on all of these
    private final ObservableBoolean isUsernameValid = new ObservableBoolean();
    private final ObservableBoolean isPasswordValid = new ObservableBoolean();
    private final ObservableBoolean arePasswordsSame = new ObservableBoolean();

    public RegisterViewModel(){
        //Can this binding be made observable so that isValid automatically 
        //updates whenever isUsernameValid/isPasswordValid/arePasswordsSame change?
        isValid = new ObservableBoolean(isUsernameValid.get() && 
                isPasswordValid.get() && 
                arePasswordsSame.get());

    }

    public void updateUsername(String name) {
        username.set(name);
        isUsernameValid.set(ValidationUtils.validateUsername(name));
        updateDependents();
    }

    public void updatePassword(String pwd) {
        password.set(pwd);
        isPasswordValid.set(ValidationUtils.validatePassword(pwd));
        updateDependents();
    }

    public void updateConfirmPassword(String cnf) {
        confirmPassword.set(cnf);
        arePasswordsSame.set(password.get().equalsIgnoreCase(cnf.get()));
        updateDependents();
    }

    //Looking to avoid this altogether
    private void updateDependents() {
        isValid.set(isUsernameValid.get() && 
                isPasswordValid.get() && 
                arePasswordsSame.get());
    }
}

3 个答案:

答案 0 :(得分:15)

在Android数据绑定中使用绑定语法无法对两个ObservableField进行数据绑定。但是,您可以使用代码绑定它们:

class RegisterViewModel {
    public final ObservableField<String> username = new ObservableField<>();
    public final ObservableField<String> password = new ObservableField<>();
    public final ObservableField<String> confirmPassword = new ObservableField<>();
    public final ObservableBoolean isValid = new ObservableBoolean();

    private boolean isUsernameValid;
    private boolean isPasswordValid;
    private boolean arePasswordsSame;

    public RegisterViewModel() {
        // You can use 3 different callbacks, but I'll use just one here
        // with 'if' statements -- it will save allocating 2 Object.
        OnPropertyChangedCallback callback = new OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (sender == username) {
                    isUsernameValid = ValidationUtils.validateUsername(name);
                } else if (sender == password) {
                    isPasswordValid = ValidationUtils.validatePassword(pwd);
                } else if (sender == confirmPassword) {
                    arePasswordsSame = password.get()
                        .equalsIgnoreCase(confirmPassword.get());
                } else {
                    // shouldn't get here...
                }
                isValid.set(isUsernameValid && isPasswordValid && arePasswordsSame);
            }
        };

        username.addOnPropertyChangedCallback(callback);
        password.addOnPropertyChangedCallback(callback);
        confirmPassword.addOnPropertyChangedCallback(callback);
    }
}

在这里,我假设空用户名,密码和confirmPassword无效。看似安全的假设。

我没有看到私人ObservableField的巨大需求。 ObservableField旨在被UI绑定,如果不能,您可以使用其他数据类型。如果您发现它们对使用上述回调的内部绑定很有用,那就去吧。

答案 1 :(得分:4)

我建议重新考虑你的方法。数据绑定的主要好处之一是允许更具表现力的视图代码(在这种情况下为XML)。虽然在XML与视图模型中实际需要做多少工作之间存在平衡,但您的案例是在视图模型中完成太多工作的完美示例。在您的代码中,不是依赖于其他字段的可观察字段,而是依赖于其他视图数据的视图数据。可观察字段只是该数据的表示,如果可能,您应该在视图层而不是数据层中创建依赖关系。

我建议的方法是从视图层(XML)开始,并假设您没有整体视图模型,只有数据附加到视图。对于例如你可以从这样的事情开始:

<layout>
    <LinearLayout>
        <EditText android:text="@{username}"/>
        <EditText text="@{password}" />
        <EditText text="@{confirmPassword}" />
        <Button android:text="Register" android:enabled="@{password.equals(confirmPassword) && ...password validation...}" />
    </LinearLayout>
</layout>

在第一步之后,您将很快意识到密码验证逻辑在这里没有意义,因为它不是微不足道的,所以你会去:

<layout>
    <import "com.example.ValidationUtils"/>
    <LinearLayout>
        <EditText android:text="@{username}"/>
        <EditText text="@{password}" />
        <EditText text="@{confirmPassword}" />
        <Button android:text="Register" android:enabled="@{password.equals(confirmPassword) && ValidationUtils.validatePassword(password)}" />
</LinearLayout>

此时,你只需要一个容器来输入用户名,密码和confirmPassword字段,这样你就可以传递它们,所以你只需添加viewModel变量。

<layout>
    <import "com.example.ValidationUtils"/>
    <variable name="viewModel" type="com.example.Register"/>
    <LinearLayout>
        <EditText android:text="@{viewModel.username}"/>
        <EditText text="@{viewModel.password}" />
        <EditText text="@{viewModel.confirmPassword}" />
        <Button android:text="Register" android:enabled="@{password.equals(confirmPassword) && ValidationUtils.validatePassword(password)}" />
</LinearLayout>

注意它有多好,甚至根本不需要查看Java代码。

P.S:如果你愿意,你也可以将启用的表达式替换为ValidationUtils.validateEntries(username, password, confirmPassword)之类的东西。这更像是一种风格选择;它不会对视图代码的表现力产生太大影响,任何阅读XML的人都可以弄清楚视图在没有多个位置的情况下试图实现的目标。

答案 2 :(得分:0)

使用@Bindable注释和Observable接口。它避免了样板代码,你可以将它用于原始数据类型。

/**
 * Created by Amardeep on 11/2/16.
*/
public class Cart implements Observable {

private final PropertyChangeRegistry mPropertyChangeRegistry = 
new PropertyChangeRegistry();

@Bindable
private int itemCount;

@Bindable 
private String name;


public int getItemCount() {
    return itemCount;
}

public void setItemCount(int itemCount) {
    this.itemCount = itemCount;
    mPropertyChangeRegistry.notifyChange(this, BR.itemCount);
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
    mPropertyChangeRegistry.notifyChange(this, BR.name);
}

@Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {

    mPropertyChangeRegistry.add(callback);
}

@Override
public void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) {

    mPropertyChangeRegistry.remove(callback);
}
}