使用 Android 数据绑定创建双向绑定
Create two-way binding with Android Data Binding
我已经实现了新的Android数据绑定,实现后发现它不支持双向绑定。我曾尝试手动解决此问题,但我正在努力寻找绑定到 EditText 时使用的好的解决方案。
在我的布局中,我有这个视图:
<EditText
android:id="@+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="@{statement.firstName}"/>
另一个视图也在显示结果:
<TextView
style="@style/Text.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{statement.firstName}"/>
在我的片段中,我创建了这样的绑定:
FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);
这有效并将 firstName 的当前值放入 EditText。问题是如何在文本更改时更新模型。我尝试在 editText 上放置一个 OnTextChanged 侦听器并更新模型。这创建了一个循环杀死我的应用程序(模型更新更新 GUI,调用 textChanged 时间无限)。接下来我尝试只在真正发生变化时才通知,如下所示:
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
boolean changed = !TextUtils.equals(this.firstName, firstName);
this.firstName = firstName;
if(changed) {
notifyPropertyChanged(BR.firstName);
}
}
这样效果更好,但每次我写一封信时,GUI 都会更新,并且由于某种原因,编辑光标会移到前面。
欢迎提出任何建议
@Gober android数据绑定支持双向绑定。因此,您无需手动制作。正如您尝试将 OnTextChanged 侦听器放在 editText 上一样。它应该更新模型。
I tried putting an OnTextChanged-listener on the editText and updating
the model. This created a loop killing my app (model-update updates
the GUI, which calls textChanged times infinity).
值得注意的是,实现双向绑定的绑定框架通常会为您进行此检查……
这是修改后的视图模型的示例,如果更改源自观察程序,它不会引发数据绑定通知:
让我们创建一个只需要重写一个方法的 SimpleTextWatcher:
public abstract class SimpleTextWatcher implements TextWatcher {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
onTextChanged(s.toString());
}
public abstract void onTextChanged(String newValue);
}
接下来,我们可以在视图模型中创建一个公开观察者的方法。观察者将配置为将控件的更改值传递给视图模型:
@Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
setUsername(newValue);
}
};
}
最后,在视图中,我们可以使用 addTextChangeListener 将观察者绑定到 EditText:
<!-- most attributes removed -->
<EditText
android:id="@+id/input_username"
android:addTextChangedListener="@{viewModel.onUsernameChanged}"/>
下面是解决通知无限的视图模型的实现。
public class LoginViewModel extends BaseObservable {
private String username;
private String password;
private boolean isInNotification = false;
private Command loginCommand;
public LoginViewModel(){
loginCommand = new Command() {
@Override
public void onExecute() {
Log.d("db", String.format("username=%s;password=%s", username, password));
}
};
}
@Bindable
public String getUsername() {
return this.username;
}
@Bindable
public String getPassword() {
return this.password;
}
public Command getLoginCommand() { return loginCommand; }
public void setUsername(String username) {
this.username = username;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.username);
}
public void setPassword(String password) {
this.password = password;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.password);
}
@Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
isInNotification = true;
setUsername(newValue);
isInNotification = false;
}
};
}
@Bindable
public TextWatcher getOnPasswordChanged() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
isInNotification = true;
setPassword(newValue);
isInNotification = false;
}
};
}
}
我希望这就是您正在寻找的并且一定能帮到您。谢谢
编辑 2016 年 5 月 4 日:
Android数据绑定现在支持自动绑定两种方式!
只需替换:
android:text="@{viewModel.address}"
与:
android:text="@={viewModel.address}"
例如在 EditText 中,您将获得双向绑定。确保更新到最新版本的 Android Studio/gradle/build-tools 以启用此功能。
(上一个答案):
我尝试了 Bhavdip Pathar 的解决方案,但这未能更新我绑定到同一变量的其他视图。我通过创建自己的 EditText 以不同的方式解决了这个问题:
public class BindableEditText extends EditText{
public BindableEditText(Context context) {
super(context);
}
public BindableEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean isInititalized = false;
@Override
public void setText(CharSequence text, BufferType type) {
//Initialization
if(!isInititalized){
super.setText(text, type);
if(type == BufferType.EDITABLE){
isInititalized = true;
}
return;
}
//No change
if(TextUtils.equals(getText(), text)){
return;
}
//Change
int prevCaretPosition = getSelectionEnd();
super.setText(text, type);
setSelection(prevCaretPosition);
}}
使用此解决方案,您可以以任何方式更新模型(TextWatcher、OnTextChangedListener 等),它会为您处理无限更新循环。使用此解决方案,模型-setter 可以简单地实现为:
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
这会减少模型中的代码-class(您可以将侦听器保留在片段中)。
对于我的问题的任何评论、改进或other/better解决方案,我将不胜感激
POJO:
public class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public User(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);
}
布局:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=First_NAME}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName, default=LAST_NAME}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editFirstName"
android:text="@{user.firstNameWatcher.value}"
android:addTextChangedListener="@{user.firstNameWatcher}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editLastName"
android:text="@{user.lastNameWatcher.value}"
android:addTextChangedListener="@{user.lastNameWatcher}"/>
观察者:
public class TextWatcherAdapter implements TextWatcher {
public final ObservableField<String> value =
new ObservableField<>();
private final ObservableField<String> field;
private boolean isInEditMode = false;
public TextWatcherAdapter(ObservableField<String> f) {
this.field = f;
field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (isInEditMode){
return;
}
value.set(field.get());
}
});
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//
}
@Override public void afterTextChanged(Editable s) {
if (!Objects.equals(field.get(), s.toString())) {
isInEditMode = true;
field.set(s.toString());
isInEditMode = false;
}
}
}
有一个更简单的解决方案。如果它没有真正改变,就避免更新字段。
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
if(this.firstName.equals(firstName))
return;
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
当使用 gradle 插件 2.1+
时,Android Studio 2.1+ 现在支持此功能
只需将 EditText 的文本属性从 @{}
更改为 @={}
,如下所示:
<EditText
android:id="@+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="@={statement.firstName}"/>
有关详细信息,请参阅:https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/
我努力寻找 2 向数据绑定的完整示例。我希望这有帮助。
完整的文档在这里:
https://developer.android.com/topic/libraries/data-binding/index.html
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.abc.twowaydatabinding.Item" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={item.name}"
android:textSize="20sp" />
<Switch
android:id="@+id/switch_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={item.checked}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change"
android:onClick="button_onClick"/>
</LinearLayout>
</layout>
Item.java:
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class Item extends BaseObservable {
private String name;
private Boolean checked;
@Bindable
public String getName() {
return this.name;
}
@Bindable
public Boolean getChecked() {
return this.checked;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
public void setChecked(Boolean checked) {
this.checked = checked;
notifyPropertyChanged(BR.checked);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
public Item item;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
item = new Item();
item.setChecked(true);
item.setName("a");
/* By default, a Binding class will be generated based on the name of the layout file,
converting it to Pascal case and suffixing “Binding” to it.
The above layout file was activity_main.xml so the generate class was ActivityMainBinding */
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setItem(item);
}
public void button_onClick(View v) {
item.setChecked(!item.getChecked());
item.setName(item.getName() + "a");
}
}
build.gradle:
android {
...
dataBinding{
enabled=true
}
}
我已经实现了新的Android数据绑定,实现后发现它不支持双向绑定。我曾尝试手动解决此问题,但我正在努力寻找绑定到 EditText 时使用的好的解决方案。 在我的布局中,我有这个视图:
<EditText
android:id="@+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="@{statement.firstName}"/>
另一个视图也在显示结果:
<TextView
style="@style/Text.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{statement.firstName}"/>
在我的片段中,我创建了这样的绑定:
FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);
这有效并将 firstName 的当前值放入 EditText。问题是如何在文本更改时更新模型。我尝试在 editText 上放置一个 OnTextChanged 侦听器并更新模型。这创建了一个循环杀死我的应用程序(模型更新更新 GUI,调用 textChanged 时间无限)。接下来我尝试只在真正发生变化时才通知,如下所示:
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
boolean changed = !TextUtils.equals(this.firstName, firstName);
this.firstName = firstName;
if(changed) {
notifyPropertyChanged(BR.firstName);
}
}
这样效果更好,但每次我写一封信时,GUI 都会更新,并且由于某种原因,编辑光标会移到前面。
欢迎提出任何建议
@Gober android数据绑定支持双向绑定。因此,您无需手动制作。正如您尝试将 OnTextChanged 侦听器放在 editText 上一样。它应该更新模型。
I tried putting an OnTextChanged-listener on the editText and updating the model. This created a loop killing my app (model-update updates the GUI, which calls textChanged times infinity).
值得注意的是,实现双向绑定的绑定框架通常会为您进行此检查……
这是修改后的视图模型的示例,如果更改源自观察程序,它不会引发数据绑定通知:
让我们创建一个只需要重写一个方法的 SimpleTextWatcher:
public abstract class SimpleTextWatcher implements TextWatcher {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
onTextChanged(s.toString());
}
public abstract void onTextChanged(String newValue);
}
接下来,我们可以在视图模型中创建一个公开观察者的方法。观察者将配置为将控件的更改值传递给视图模型:
@Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
setUsername(newValue);
}
};
}
最后,在视图中,我们可以使用 addTextChangeListener 将观察者绑定到 EditText:
<!-- most attributes removed -->
<EditText
android:id="@+id/input_username"
android:addTextChangedListener="@{viewModel.onUsernameChanged}"/>
下面是解决通知无限的视图模型的实现。
public class LoginViewModel extends BaseObservable {
private String username;
private String password;
private boolean isInNotification = false;
private Command loginCommand;
public LoginViewModel(){
loginCommand = new Command() {
@Override
public void onExecute() {
Log.d("db", String.format("username=%s;password=%s", username, password));
}
};
}
@Bindable
public String getUsername() {
return this.username;
}
@Bindable
public String getPassword() {
return this.password;
}
public Command getLoginCommand() { return loginCommand; }
public void setUsername(String username) {
this.username = username;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.username);
}
public void setPassword(String password) {
this.password = password;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.password);
}
@Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
isInNotification = true;
setUsername(newValue);
isInNotification = false;
}
};
}
@Bindable
public TextWatcher getOnPasswordChanged() {
return new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
isInNotification = true;
setPassword(newValue);
isInNotification = false;
}
};
}
}
我希望这就是您正在寻找的并且一定能帮到您。谢谢
编辑 2016 年 5 月 4 日: Android数据绑定现在支持自动绑定两种方式! 只需替换:
android:text="@{viewModel.address}"
与:
android:text="@={viewModel.address}"
例如在 EditText 中,您将获得双向绑定。确保更新到最新版本的 Android Studio/gradle/build-tools 以启用此功能。
(上一个答案):
我尝试了 Bhavdip Pathar 的解决方案,但这未能更新我绑定到同一变量的其他视图。我通过创建自己的 EditText 以不同的方式解决了这个问题:
public class BindableEditText extends EditText{
public BindableEditText(Context context) {
super(context);
}
public BindableEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean isInititalized = false;
@Override
public void setText(CharSequence text, BufferType type) {
//Initialization
if(!isInititalized){
super.setText(text, type);
if(type == BufferType.EDITABLE){
isInititalized = true;
}
return;
}
//No change
if(TextUtils.equals(getText(), text)){
return;
}
//Change
int prevCaretPosition = getSelectionEnd();
super.setText(text, type);
setSelection(prevCaretPosition);
}}
使用此解决方案,您可以以任何方式更新模型(TextWatcher、OnTextChangedListener 等),它会为您处理无限更新循环。使用此解决方案,模型-setter 可以简单地实现为:
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
这会减少模型中的代码-class(您可以将侦听器保留在片段中)。
对于我的问题的任何评论、改进或other/better解决方案,我将不胜感激
POJO:
public class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public User(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);
}
布局:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName, default=First_NAME}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName, default=LAST_NAME}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editFirstName"
android:text="@{user.firstNameWatcher.value}"
android:addTextChangedListener="@{user.firstNameWatcher}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editLastName"
android:text="@{user.lastNameWatcher.value}"
android:addTextChangedListener="@{user.lastNameWatcher}"/>
观察者:
public class TextWatcherAdapter implements TextWatcher {
public final ObservableField<String> value =
new ObservableField<>();
private final ObservableField<String> field;
private boolean isInEditMode = false;
public TextWatcherAdapter(ObservableField<String> f) {
this.field = f;
field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (isInEditMode){
return;
}
value.set(field.get());
}
});
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//
}
@Override public void afterTextChanged(Editable s) {
if (!Objects.equals(field.get(), s.toString())) {
isInEditMode = true;
field.set(s.toString());
isInEditMode = false;
}
}
}
有一个更简单的解决方案。如果它没有真正改变,就避免更新字段。
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
if(this.firstName.equals(firstName))
return;
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
当使用 gradle 插件 2.1+
时,Android Studio 2.1+ 现在支持此功能只需将 EditText 的文本属性从 @{}
更改为 @={}
,如下所示:
<EditText
android:id="@+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="@={statement.firstName}"/>
有关详细信息,请参阅:https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/
我努力寻找 2 向数据绑定的完整示例。我希望这有帮助。 完整的文档在这里: https://developer.android.com/topic/libraries/data-binding/index.html
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.abc.twowaydatabinding.Item" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={item.name}"
android:textSize="20sp" />
<Switch
android:id="@+id/switch_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="@={item.checked}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change"
android:onClick="button_onClick"/>
</LinearLayout>
</layout>
Item.java:
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class Item extends BaseObservable {
private String name;
private Boolean checked;
@Bindable
public String getName() {
return this.name;
}
@Bindable
public Boolean getChecked() {
return this.checked;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
public void setChecked(Boolean checked) {
this.checked = checked;
notifyPropertyChanged(BR.checked);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
public Item item;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
item = new Item();
item.setChecked(true);
item.setName("a");
/* By default, a Binding class will be generated based on the name of the layout file,
converting it to Pascal case and suffixing “Binding” to it.
The above layout file was activity_main.xml so the generate class was ActivityMainBinding */
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setItem(item);
}
public void button_onClick(View v) {
item.setChecked(!item.getChecked());
item.setName(item.getName() + "a");
}
}
build.gradle:
android {
...
dataBinding{
enabled=true
}
}