edittext databinding data android android-edittext android-databinding

databinding - Crear enlace bidireccional con enlace de datos de Android



databinding android (6)

@Gober El enlace de datos de Android admite el enlace bidireccional. Por lo tanto no es necesario hacerlo manualmente. Como lo intentó, coloque OnTextChanged-listener en editText. Debe actualizar el modelo.

Intenté colocar un servicio de escucha OnTextChanged en el editText y actualizar el modelo. Esto creó un bucle que mató a mi aplicación (la actualización del modelo actualiza la GUI, que llama a textChanged times infinity).

Vale la pena señalar que los marcos de enlace que implementan enlaces de dos vías normalmente realizarían esta comprobación por usted ...

Este es el ejemplo del modelo de vista modificado, que no genera una notificación de enlace de datos si el cambio se originó en el observador:

Vamos a crear un SimpleTextWatcher que solo requiere que se sobrescriba un método:

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

A continuación, en el modelo de vista podemos crear un método que exponga al observador. El observador se configurará para pasar el valor modificado del control al modelo de vista:

@Bindable public TextWatcher getOnUsernameChanged() { return new SimpleTextWatcher() { @Override public void onTextChanged(String newValue) { setUsername(newValue); } }; }

Finalmente, en la vista podemos vincular al observador con el EditText usando addTextChangeListener:

<!-- most attributes removed --> <EditText android:id="@+id/input_username" android:addTextChangedListener="@{viewModel.onUsernameChanged}"/>

Aquí está la implementación del Modelo de vista que resuelve el infinito de notificación.

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

Espero que esto es lo que buscas y seguro te pueda ayudar. Gracias

He implementado el nuevo enlace de datos de Android y, después de implementarlo, me di cuenta de que no admite el enlace bidireccional. He intentado resolver esto manualmente, pero estoy luchando para encontrar una buena solución para usar cuando se enlaza con un EditText. En mi diseño tengo esta vista:

<EditText android:id="@+id/firstname" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textCapWords|textNoSuggestions" android:text="@{statement.firstName}"/>

Otra vista también muestra los resultados:

<TextView style="@style/Text.Large" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{statement.firstName}"/>

En mi fragmento creo el enlace así:

FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false); binding.setStatement(mCurrentStatement);

Esto funciona y coloca el valor actual de firstName en el EditText. El problema es cómo actualizar el modelo cuando cambia el texto. Intenté colocar un servicio de escucha OnTextChanged en el editText y actualizar el modelo. Esto creó un bucle que mató a mi aplicación (la actualización del modelo actualiza la GUI, que llama a textChanged times infinity). A continuación traté de notificar solo cuando ocurrieron cambios reales como este:

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

Esto funcionó mejor, pero cada vez que escribo una letra, la GUI se actualiza y, por alguna razón, el cursor de edición se mueve al frente.

Cualquier sugerencia será bienvenida


EDITAR 04.05.16: ¡El enlace de datos de Android ahora admite enlaces de dos vías automáticamente! Simplemente reemplace:

android:text="@{viewModel.address}"

con:

android:text="@={viewModel.address}"

en un texto de edición, por ejemplo, y obtienes un enlace bidireccional. Asegúrate de actualizar a la última versión de Android Studio / gradle / build-tools para habilitar esto.

(RESPUESTA ANTERIOR):

Intenté la solución de Bhavdip Pathar, pero esto no logró actualizar otras vistas que había vinculado a la misma variable. Resolví esto de una manera diferente, creando mi propio 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); }}

Con esta solución, puede actualizar el modelo de la forma que desee (TextWatcher, OnTextChangedListener, etc.) y se encarga del bucle de actualización infinito. Con esta solución, el configurador de modelos se puede implementar simplemente como:

public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName); }

Esto coloca menos código en la clase modelo (puede mantener a los oyentes en su Fragmento).

Agradecería cualquier comentario, mejora u otra / mejor solución a mi problema.


Esto ahora es compatible con Android Studio 2.1+ cuando se usa el plugin gradle 2.1+

Simplemente cambie el atributo de texto del texto de edición de @{} a @={} esta manera:

<EditText android:id="@+id/firstname" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textCapWords|textNoSuggestions" android:text="@={statement.firstName}"/>

Para obtener más información, consulte: halfthought.wordpress.com/2016/03/23/…


Hay una solución más simple. Solo evita actualizar el campo si realmente no ha cambiado.

@Bindable public String getFirstName() { return firstName; } public void setFirstName(String firstName) { if(this.firstName.equals(firstName)) return; this.firstName = firstName; notifyPropertyChanged(BR.firstName); }


Luché por encontrar un ejemplo completo de enlace de datos bidireccional. Espero que esto ayude. La documentación completa está aquí: 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 } }


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

Diseño:

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

Vigilante

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