使用Android数据绑定创建双向绑定

时间:2015-10-27 07:57:27

标签: android android-edittext android-databinding

我已经实现了新的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都会更新,因为编辑光标会移到前面。

欢迎任何建议

6 个答案:

答案 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)

@Gober android数据绑定支持双向绑定。因此,您无需手动进行。当您尝试将OnTextChanged-listener放在editText上时。它应该更新模型。

  

我尝试在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
    }

}
相关问题