Курсор Edittext сбрасывается налево после обновления привязки данных Android

Я тестирую новую библиотеку привязки данных Android (1.0-rc1), и я создал объект User с тремя строковыми полями (имя, адрес электронной почты и возраст) и связал их с 3 EditTexts в моем макете.

В первом поле (имя) я разместил TextWatcher. Кажется, все работает хорошо. Я запретил цикл notifyPropertyChanged в поле имени, проверяя, отличается ли текст, прежде чем позволить ему вызвать setName.

Проблема заключается в том, что каждый раз, когда я ввожу имя поля, курсор сбрасывается слева от EditText после каждого символа. Я googled вокруг для решения, но большинство исправить предложения по проблеме курсора говорят, чтобы захватить ссылку на EditText и отрегулировать позицию курсора вручную. Но я бы хотел избежать этого, так как мне тогда нужно findViewByID в EditText, и точка привязки данных должна была попытаться избежать этого. Благодарим вас за помощь.

Мой макет выглядит следующим образом:

<layout>

<data>
    <variable name="user" type="com.carlpoole.databindingstest.User"/>
</data>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <EditText
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/name"
        android:text="@{user.name}"
        bind:addTextChangedListener="@{user.nameChanged}"
        />

    <EditText
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/email"
        android:layout_below="@+id/name"
        android:text="@{user.email}"/>

    <EditText
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/age"
        android:layout_below="@+id/email"
        android:text="@{user.age}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/age"
        android:text="@{user.name}"/>

</RelativeLayout>

Мой пользовательский объект выглядит следующим образом:

import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.text.Editable;
import android.text.TextWatcher;

public class User extends BaseObservable {

    private String name;
    private String email;
    private String age;

    public User(String name, String email, String age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public User(){};

    @Bindable
    public String getName() {
        return name;
    }

    @Bindable
    public String getEmail() {
        return email;
    }

    @Bindable
    public String getAge() {
        return age;
    }

    public final TextWatcher nameChanged = new TextWatcher() {
        @Override
        public void afterTextChanged(Editable s) {
            if(!s.toString().equalsIgnoreCase(name))
                setName(s.toString());
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {}
    };

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(com.carlpoole.databindingstest.BR.name);
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

Моя активность выглядит следующим образом

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.carlpoole.databindingstest.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("Carl Poole", "[email protected]", "26");
        binding.setUser(user);

    }
}

Ответ 1

Лучше всего использовать пользовательский @BindingAdapter, который уже будет иметь ссылку на EditText. Таким образом, вы можете избежать повторного связывания, если текст в EditText соответствует вашей модели, что позволит решить проблему с курсором.

Сначала измените android:text="@{user.name}" на bind:binding="@{user.name}". Затем добавьте этот статический метод в любом месте вашего проекта. Мы сохраняем их все в классе под названием BindingAdapters.java. Кстати, начиная с RC2 вы можете создавать нестатические методы привязки привязки, но это, вероятно, сейчас не самая большая проблема.

@BindingAdapter("binding")
public static void bindEditText(EditText editText, CharSequence value) {
  if (!editText.getText().toString().equals(value.toString())) {
    editText.setText(value);
  }
}

Ответ 2

Чтобы исправить странное поведение привязки данных, которое сбрасывает курсор в начало EditText, вы можете добавить следующий InverseBindingAdapter:

  @SuppressLint("RestrictedApi")
  @BindingAdapter("android:text")
  public static void setText(EditText view, String oldText, String text) {

    TextViewBindingAdapter.setText(view, text);
    if (text == null) return;
    if (text.equals(oldText) || oldText == null) {
      view.setSelection(text.length());
    }
  }

Ответ 3

Проблема связана с путаницей сеттера: ваш сеттер, как рекомендовано в документации DataBinding, вызывает метод notifyPropertyChanged. Но метод notifyPropertyChanged, среди прочего, сбрасывает курсор, который вызывает проблемы. Их нет необходимости в том, чтобы сеттер обновлял пользовательский интерфейс, когда это был пользовательский интерфейс (TextWatcher), вызывающий сеттер. Тогда решение состоит в том, чтобы сеттеры вызывали только метод notifyPropertyChanged, когда некоторые вычисления/манипуляции с базой данных должны приводить к обновлению пользовательского интерфейса.

Ответ 4

Попробуй это:

@BindingAdapter("android:text")
fun setStringWIthSelection(view: EditText, str : String) {
    view.setText(str)
    view.setSelection(view.text.length)
}