【问题标题】:Create two-way binding with Android Data Binding使用 Android 数据绑定创建双向绑定
【发布时间】:2015-10-27 07:57:27
【问题描述】:

我已经实现了新的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 times infinity)。接下来,我尝试仅在发生真正的更改时通知:

@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 都会更新,并且出于某种原因,编辑光标移到了前面。

欢迎提出建议

【问题讨论】:

  • 你的吸气剂在哪里。有没有加@Bindable注解?
  • 是的。现在将 getter 添加到描述中。
  • 您总是调用this.firstName = firstName,尽管它上面有布尔值。你研究过这个逻辑吗?
  • 它并没有真正影响绑定部分,但我明白你的意思。在我的解决方案中,布尔值已被删除。

标签: android android-edittext android-databinding


【解决方案1】:

编辑 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 中)。

如果我的问题有任何 cmets、改进或其他/更好的解决方案,我将不胜感激

【讨论】:

【解决方案2】:

使用 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/

【讨论】:

  • 感觉就像 Angular 一样。两种方式绑定简化了开发。
【解决方案3】:

@Gober android 数据绑定支持两种方式绑定。因此,您无需手动制作。正如您尝试将 OnTextChanged-listener 放在 editText 上一样。它应该更新模型。

我尝试在 editText 上放置一个 OnTextChanged-listener 并更新 该模型。这创建了一个循环杀死我的应用程序(模型更新更新 GUI,它调用 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 将 watcher 绑定到 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;
            }
        };
    }
}

我希望这是您正在寻找的内容,并且肯定可以帮助您。谢谢

【讨论】:

  • 感谢您的详尽回答。它看起来像是一个可行的解决方案,但不幸的是它不适用于我的用例。首先,Android Data Binding 本身不支持双向绑定,因为您和我都清楚地自己实现了这一点。其次,我对这个解决方案的问题是其他视图也绑定到同一个变量。我有一个更新数据的 EditText 和一个输出相同数据的 TextView。使用此解决方案,TextView 不会更新。但是,如果您只有一个与变量的绑定,这是一个很好的解决方案!
  • 嗨!当我将它添加到布局 xml 时,Android Studio 说“未知属性 android:addTextChangedListener”。还有什么可以让它发挥作用吗?谢谢。
  • 我尝试了同样的方法,它进入了一个循环,我从文本观察器设置模型类,它会更新 UI,然后再次调用 texwatcher 的 onTextChanged() 方法?
  • Android Studio 2.1 preview 3(或更高版本)现在支持它。看halfthought.wordpress.com/2016/03/23/…
【解决方案4】:

有一个更简单的解决方案。如果它没有真正改变,请避免更新字段。

@Bindable
public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
     if(this.firstName.equals(firstName))
        return;

     this.firstName = firstName;
     notifyPropertyChanged(BR.firstName);
}

【讨论】:

  • 请看我原始问题的底部。这是我的第一次尝试,但每次有变化(即写每个字母)时,光标都会跳到编辑文本输入的前面。
  • 我最终使用的解决方案基本上是这样,但之后再次将光标移到末尾
  • 在recyclerview里面?好吧,不幸的是,它似乎没有
【解决方案5】:

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;
        }
    }

}

【讨论】:

  • 不鼓励转储代码 - 请提供一些解释以帮助提高此答案的质量。
【解决方案6】:

我很难找到 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
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-01
    • 1970-01-01
    • 2016-06-23
    • 2019-12-24
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多