2016-11-13 1 views
2

J'ai du mal à obtenir des fonctionnalités pour travailler avec les filateurs Android lorsqu'ils sont configurés avec la liaison de données bidirectionnelle. Je voudrais définir la valeur initiale du spinner via la liaison de données bidirectionnelle sur android:selectedItemPosition. Les entrées spinner sont initialisées par ViewModel et sont remplies correctement, par conséquent la liaison de données semble fonctionner correctement.Sélection du paramètre Spinner Android avec une liaison bidirectionnelle

Le problème est lié à la liaison bidirectionnelle de selectedItemPosition. La variable est initialisée à 5 par ViewModel mais l'élément sélectionné du spinner reste à 0 (le premier élément). Lors du débogage, il apparaît que la valeur de ObservableInt est initialement 5 (comme définie) mais est réinitialisée à zéro pendant la deuxième phase de executeBindings.

Toute aide serait appréciée.

test_spinner_activity.xml

<layout xmlns:tools="http://schemas.android.com/tools" 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto"> 

    <data> 
     <variable name="viewModel" 
        type="com.aapp.viewmodel.TestSpinnerViewModel"/> 
    </data> 
    <LinearLayout android:layout_width="match_parent" 
        android:layout_height="wrap_content"> 
     <android.support.v7.widget.AppCompatSpinner 
      android:layout_width="wrap_content" 
      android:layout_height="match_parent" 
      android:id="@+id/sTimeHourSpinner" 
      android:selectedItemPosition="@={viewModel.startHourIdx}" 
      android:entries="@{viewModel.startTimeHourSelections}"/> 
    </LinearLayout> 
</layout> 

TestSpinnerViewModel.java

public class TestSpinnerViewModel { 
    public final ObservableArrayList<String> startTimeHourSelections = new ObservableArrayList<>(); 
    public final ObservableInt startHourIdx = new ObservableInt(); 

    public TestSpinnerViewModel(Context context) { 
     this.mContext = context; 

     for (int i=0; i < 24; i++) { 
      int hour = i; 
      startTimeHourSelections.add(df.format(hour)); 
     } 
     startHourIdx.set(5); 
    } 
} 

TestSpinnerActivity.java

public class TestSpinnerActivity extends AppCompatActivity { 
    private TestSpinnerActivityBinding binding; 
    private TestSpinnerViewModel mTestSpinnerViewModel; 

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

     binding = DataBindingUtil.bind(findViewById(R.id.test_spinner)); 
     mTestSpinnerViewModel = new TestSpinnerViewModel(this); 
     binding.setViewModel(mTestSpinnerViewModel); 
    } 

Je Android Studio 2.2.2 et Databinding est activé.

+0

Juste un heads-up, ce n'est pas databinding dans les deux sens, mais juste _databinding_. Dans les deux sens implique de changer l'objet change l'interface utilisateur. – Chisko

+0

Salut @chisko, je suis confus par ton commentaire. En utilisant la dernière version d'Android Studio et les bibliothèques de liaison, vous déclarez simplement une variable dans la mise en page en utilisant "@ = {variable}" comme je l'ai fait avec 'android: selectedItemPosition =" @ = {viewModel.startHourIdx} "' génère le code de la plaque de la chaudière pour le faire 2 voies. –

+0

Non. Docs Android sont un peu déroutant et trompeur à ce sujet. Ce que vous décrivez ci-dessus est une liaison à sens unique, dans laquelle les modifications de l'interface utilisateur sont automatiquement répercutées sur le modèle. – Chisko

Répondre

3

merci pour vos suggestions. Mais j'ai trouvé la réponse à ma propre question. Il s'avère que la raison pour laquelle la variable android:[email protected]={viewModel.startHourIdx} a été réinitialisée à partir de la valeur initialisée de 5 à 0 est due à l'ordre de déclaration des attributs selectedItemPosition et entries. Dans mon exemple, ils ont été déclarés dans cet ordre spécifique et le code de liaison généré automatiquement produit l'initialisation dans le même ordre.

Par conséquent, même si le selectedItemPosition a été réglée correctement l'initialisation du entries provoque instanciation du un ArrayAdapter qui remet à zéro le selectedItemPosition à 0.

Par conséquent, le correctif est d'échanger les deux déclarations d'attributs dans le fichier de mise en page .

<data> 
    <variable name="viewModel" 
       type="com.aapp.viewmodel.TestSpinnerViewModel"/> 
</data> 
<LinearLayout android:layout_width="match_parent" 
       android:layout_height="wrap_content"> 
    <android.support.v7.widget.AppCompatSpinner 
     android:layout_width="wrap_content" 
     android:layout_height="match_parent" 
     android:id="@+id/sTimeHourSpinner" 
     android:entries="@{viewModel.startTimeHourSelections}" 
     android:selectedItemPosition="@={viewModel.startHourIdx}"/> 
</LinearLayout> 

0

J'ai récemment créé une application de démonstration sur GitHub pour montrer comment réaliser une liaison de données bidirectionnelle sur les processeurs en utilisant le mécanisme bindingAdapter et InverseBindingAdapter.

Dans cette application, je ne lie pas l'attribut "android: selectedItemPosition" mais lie l'élément sélectionné lui-même (en utilisant la classe ObservableField) du spinner comme indiqué dans l'extrait ci-dessous. Comme il s'agit d'une liaison bidirectionnelle, en affectant une valeur initiale au ObservableField lié (c'est-à-dire l'élément sélectionné) lors de l'installation de l'adaptateur spinner, avec une gestion spéciale dans bindingAdapter du spinner, la sélection initiale du spinner peut être réalisée.

N'hésitez pas à consulter l'application de démonstration here pour plus de détails.

acivity_main.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android" 
     xmlns:tools="http://schemas.android.com/tools" 
     xmlns:app="http://schemas.android.com/apk/res-auto" 
     xmlns:bind="http://schemas.android.com/apk/res-auto"> 

    <data> 
     <variable 
      name="bindingPlanet" 
      type="au.com.chrisli.spinnertwowaydatabindingdemo.BindingPlanet"/> 
     <variable 
      name="spinAdapterPlanet" 
      type="android.widget.ArrayAdapter"/> 
    </data> 

    <RelativeLayout 
     android:id="@+id/activity_main" 
     android:layout_width="match_parent" 
     android:layout_height="match_parent" 
     ...> 

     <android.support.v7.widget.AppCompatSpinner 
      android:id="@+id/spin" 
      android:layout_width="match_parent" 
      android:layout_height="wrap_content" 
      android:layout_centerInParent="true" 
      style="@style/Base.Widget.AppCompat.Spinner.Underlined" 
      bind:selectedPlanet="@={bindingPlanet.obvSelectedPlanet_}" 
      app:adapter="@{spinAdapterPlanet}"/> 

     ...(not relevant content omitted for simplicity) 
    </RelativeLayout> 

</layout> 

manipulation spéciale à l'intérieur de l'adaptateur de liaison dans BindingPlanet.java

public final ObservableField<Planet> obvSelectedPlanet_ = new ObservableField<>(); //for simplicity, we use a public variable here 

private static class SpinPlanetOnItemSelectedListener implements AdapterView.OnItemSelectedListener { 

    private Planet initialSelectedPlanet_; 
    private InverseBindingListener inverseBindingListener_; 

    public SpinPlanetOnItemSelectedListener(Planet initialSelectedPlanet, InverseBindingListener inverseBindingListener) { 
     initialSelectedPlanet_ = initialSelectedPlanet; 
     inverseBindingListener_ = inverseBindingListener; 
    } 

    @Override 
    public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { 
     if (initialSelectedPlanet_ != null) { 
      //Adapter is not ready yet but there is already a bound data, 
      //hence we need to set a flag so we can implement a special handling inside the OnItemSelectedListener 
      //for the initial selected item 
      Integer positionInAdapter = getPlanetPositionInAdapter((ArrayAdapter<Planet>) adapterView.getAdapter(), initialSelectedPlanet_); 
      if (positionInAdapter != null) { 
       adapterView.setSelection(positionInAdapter); //set spinner selection as there is a match 
      } 
      initialSelectedPlanet_ = null; //set to null as the initialization is done 
     } else { 
      if (inverseBindingListener_ != null) { 
       inverseBindingListener_.onChange(); 
      } 
     } 
    } 

    @Override 
    public void onNothingSelected(AdapterView<?> adapterView) {} 
} 

@BindingAdapter(value = {"bind:selectedPlanet", "bind:selectedPlanetAttrChanged"}, requireAll = false) 
public static void bindPlanetSelected(final AppCompatSpinner spinner, Planet planetSetByViewModel, 
             final InverseBindingListener inverseBindingListener) { 

    Planet initialSelectedPlanet = null; 
    if (spinner.getAdapter() == null && planetSetByViewModel != null) { 
     //Adapter is not ready yet but there is already a bound data, 
     //hence we need to set a flag in order to implement a special handling inside the OnItemSelectedListener 
     //for the initial selected item, otherwise the first item will be selected by the framework 
     initialSelectedPlanet = planetSetByViewModel; 
    } 

    spinner.setOnItemSelectedListener(new SpinPlanetOnItemSelectedListener(initialSelectedPlanet, inverseBindingListener)); 

    //only proceed further if the newly selected planet is not equal to the already selected item in the spinner 
    if (planetSetByViewModel != null && !planetSetByViewModel.equals(spinner.getSelectedItem())) { 
     //find the item in the adapter 
     Integer positionInAdapter = getPlanetPositionInAdapter((ArrayAdapter<Planet>) spinner.getAdapter(), planetSetByViewModel); 
     if (positionInAdapter != null) { 
      spinner.setSelection(positionInAdapter); //set spinner selection as there is a match 
     } 
    } 
} 

@InverseBindingAdapter(attribute = "bind:selectedPlanet", event = "bind:selectedPlanetAttrChanged") 
public static Planet captureSelectedPlanet(AppCompatSpinner spinner) { 
    return (Planet) spinner.getSelectedItem(); 
} 
+0

si c'est une liaison bidirectionnelle, où sont vos appels' notifyPropertyChanged() ' ? – Chisko

+0

La clause "inverseBindingListener_.onChange()" dans onItemSelected() déclenchera l'appel de la fonction InverseBindingAdapter par l'infrastructure. Comme la fonction InverseBindingAdapter est désactivée sur le sujet en cours, elle n'est pas affichée dans l'extrait de code ci-dessus. – chrisli

+0

Saviez-vous que vous pouvez vous épargner de tout ce qui se passe avec le mécanisme actuel en passant de 'Observable' à' BaseObservable'? – Chisko