Подтвердить что ты не робот

Создание двусторонней привязки с привязкой данных Android

Я внедрил новую привязку данных для 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. Проблема заключается в том, как обновить модель при изменении текста. Я попробовал поставить OnTextChanged-listener на editText и обновить модель. Это создало цикл, убивающий мое приложение (обновление модели обновляет графический интерфейс, который вызывает textChanged times бесконечность). Затем я попытался только оповестить, когда произошли реальные изменения:

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

Это работало лучше, но каждый раз, когда я пишу письмо, графический интерфейс обновляется и по какой-то причине курсор редактирования перемещается в начало.

Любые предложения приветствуются

4b9b3361

Ответ 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);
}

Это помещает меньше кода в модельный класс (вы можете держать слушателей в своем фрагменте).

Я был бы признателен за любые комментарии, улучшения или другие/лучшие решения моей проблемы.

Ответ 2

Теперь это поддерживается в Android Studio 2.1+ при использовании плагина gradle 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/

Ответ 3

@Gober Андроидальная привязка данных поддерживает двустороннюю привязку. Поэтому вам не нужно делать это вручную. Как вы пробовали, поставив OnTextChanged-listener на editText. Он должен обновить модель.

Я попытался поместить OnTextChanged-listener в editText и обновить модель. Это создало цикл, убивающий мое приложение (обновления для обновления модели GUI, который вызывает textChanged times бесконечность).

Стоит отметить, что обязательные фреймворки, реализующие двустороннюю привязку, обычно проведут эту проверку для вас...

Вот пример модифицированной модели представления, которая не вызывает уведомление о привязке данных, если изменение возникло в наблюдателе:

Позволяет создать 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);
        }
    };
}

Наконец, в представлении мы можем привязать наблюдателя к EditText, используя addTextChangeListener:

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

Надеюсь, это то, что вы ищете, и обязательно поможет вам. Благодаря

Ответ 4

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}"/>

Watcher:

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

Существует более простое решение. Просто не обновляйте поле, если оно действительно не изменилось.

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

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

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