我已经实现了新的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-listener并更新模型。这创建了一个循环杀死我的应用程序(模型更新更新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都会更新,因为编辑光标会移到前面。
欢迎任何建议
答案 0 :(得分:79)
编辑04.05.16: 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等),并为您处理无限更新循环。使用此解决方案,模型设置器可以简单地实现为:
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
这会减少模型类中的代码(您可以将侦听器保留在Fragment中)。
我很感激我的问题的任何评论,改进或其他/更好的解决方案
答案 1 :(得分:19)
使用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 :(得分:7)
我尝试在editText上放置一个OnTextChanged-listener并进行更新 该模型。这创建了一个循环杀死我的应用程序(模型更新更新 GUI,调用textChanged乘以无穷大。)
值得注意的是,实现双向绑定的绑定框架通常会为您执行此检查...
以下是修改后的视图模型的示例,如果更改源自观察者,则不会引发数据绑定通知:
让我们创建一个只需要覆盖一个方法的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;
}
};
}
}
我希望这是你正在寻找的,当然可以帮助你。感谢
答案 3 :(得分:2)
有一个更简单的解决方案。如果没有真正改变,请避免更新字段。
@Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
if(this.firstName.equals(firstName))
return;
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
答案 4 :(得分:1)
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;
}
}
}
答案 5 :(得分:0)
我努力寻找双向数据绑定的完整示例。我希望这有帮助。 完整的文档在这里: 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
}
}