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

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

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

Проблема в том, что каждый раз, когда я печатаю в поле имени, курсор сбрасывается слева от EditText после каждого символа. Я погуглил в поисках решения, но в большинстве предложений по исправлению проблемы с курсором говорится, что нужно взять ссылку на EditText и настроить положение курсора вручную. Но я хотел бы избежать этого, так как мне нужно найтиViewByID для 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>

My user object looks like this:

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

    }
}

person Carl Poole    schedule 31.08.2015    source источник
comment
Я предотвратил цикл notifyPropertyChanged в поле имени, проверив, отличается ли текст, прежде чем разрешить ему вызывать setName - однако, если бы я делал ставки, я бы сказал, что именно это вызывает ваши проблемы с курсором. Почему у вас есть TextWatcher в первую очередь?   -  person CommonsWare    schedule 02.09.2015
comment
@CommonsWare TextWatcher прослушивает обновление в EditText, чтобы запустить обновление переменной имени в объекте пользователя. Без него не узнать. Если я удаляю вызов setName из метода afterTextChanged, он работает нормально, поэтому проблема может быть вызвана тем, как я использую библиотеку привязки данных.   -  person Carl Poole    schedule 02.09.2015
comment
Без этого он не будет знать -- обновите модель, когда пользователь скажет вам обновить модель (нажимает кнопку DONE, покидает действие через HOME и т. д.).   -  person CommonsWare    schedule 02.09.2015
comment
@carlpoole Это фантастический ответ для меня, хотя это вопрос. Большое спасибо за публикацию всего вашего решения. Я смог посмотреть на это и заставить мою привязку данных работать, и я не мог найти нигде другого примера, который был бы настолько ясным. Я искал книги, SO, Google, документы Android. фу... Я никогда не верну эти часы. SuperMegaUpvote за четкий и подробный вопрос-ответ. :)   -  person raddevus    schedule 13.03.2016


Ответы (6)


Лучше всего использовать пользовательский @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);
  }
}
person Jacob Tabak    schedule 02.09.2015
comment
Руководство по привязке данных по-прежнему относится к RC1. RC2 уже вышел или он еще в разработке? - person curioustechizen; 04.09.2015
comment
RC2 еще не доступен. Скоро появится в ближайшей к вам библиотеке поддержки. - person Jacob Tabak; 04.09.2015
comment
Мне пришлось использовать TextWatcher, чтобы иметь привязку данных вместе с пользовательским представлением, и обнаружил ту же проблему. Ваше решение отлично сработало для меня. Спасибо. - person rajath; 12.01.2017
comment
У меня похожая проблема, я решил ее, проверив, совпадает ли значение с предыдущим, как и код в ответе... - person clzola; 10.09.2018

Чтобы исправить странное поведение привязки данных, которое сбрасывает курсор в начало 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());
    }
  }
person Saif C    schedule 30.06.2017
comment
Это похоже на чистое решение - person Aditya Ladwa; 11.08.2017
comment
Большое тебе спасибо. Это здорово. Но если вы наберете текст в середине, курсор переместится в крайнее правое положение. - person MJ Studio; 25.06.2019
comment
Отличное решение, большое спасибо. Как-то странно, что это упускается из виду со встроенным адаптером для привязки. - person uberchilly; 10.01.2020

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

person HeavyD    schedule 22.10.2015

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

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

Мы можем сделать это без каких-либо новых BindingAdapter. Ниже мой EditText в XML. А viewModel — это моя переменная DataBinding. И я установлю курсор в android:onTextChanged в самом XML

<androidx.appcompat.widget.AppCompatEditText
            android:id="@+id/et_enter_pincode"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="number"
            android:maxLength="6"
            android:text="@={viewModel.observableEditText}"
            android:textSize="16sp"
            android:onTextChanged="@{(text, start, before, count) -> viewModel.onPincodeTextChanged(etEnterPincode)}"
            android:hint="@string/enter_pincode"/>

Внутри View Model код выглядит так

val observableEditText = ObservableField<String>("")

fun onPincodeTextChanged(
        view: EditText
){
    view.setSelection(view.text.length)
}
person Mohanakrrishna    schedule 20.01.2020

Ответы других друзей великолепны, но я все еще получаю небольшую ошибку, когда использую эти методы. Когда я редактирую текст в середине представления EditText, курсор перемещается в конец, а не в место редактирования. Я добавляю исправление для этой проблемы:

        int selection = mEditText.getSelectionEnd();
        int updateTextLength = text == null ? 0 : text.length();
        mEditText.setText(text);
        mEditText.setSelection(Math.min(selection, updateTextLength));
person zz-m    schedule 19.01.2021