我理解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());
}
}
答案 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);
}
}