2016-11-13 4 views
2

2ウェイデータバインディングで設定した場合、Androidスピナーで機能するいくつかの機能を取得するのには苦労しています。私はandroid:selectedItemPositionに2ウェイデータバインディングを介してスピナーの初期値を設定したいと思います。スピナーのエントリは、ViewModelによって初期化され、正しく挿入されるため、データバインディングは正しく動作しているように見えます。2ウェイバインディングによるAndroid Spinner設定の選択

問題は、双方向バインディングselectedItemPositionにあります。変数はViewModelによって5に初期化されますが、スピナーの選択項目は0(最初の項目)のままです。デバッグすると、ObservableIntの値は最初は5に設定されているように見えますが、executeBindingsの2番目のフェーズでは0にリセットされます。

ご協力いただければ幸いです。

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

私は、Android Studioの2.2.2を使用していますが、データバインディングが有効になっています。

+0

ジャストヘッドアップ、これは、双方向のデータバインディングではなく、ただ_databinding_。双方向は、オブジェクトの変更UIを変更することを伴う。 – Chisko

+0

こんにちは@chisko、私はあなたのコメントで混乱しています。 Android Studioとバインディングライブラリの最新バージョンを使用すると、 'android:selectedItemPosition =" @ = {viewModel.startHourIdx} "'でこれを実行したように、単に "@ = {variable}"を使用してレイアウト内の変数を宣言し、ボイラープレートコードを生成して2方向にします。 –

+0

いいえ、Androidのドキュメントはこれについて少し混乱し、誤解を招くことがあります。上記で説明しているのは、UIへの変更が自動的にモデルに反映される一方向バインディングです。 – Chisko

答えて

3

で結合アダプタ内の特別な処理をご提案していただきありがとうございます。しかし私は自分の質問に対する答えを見つけました。 android:[email protected]={viewModel.startHourIdx}変数が初期化された値5から0にリセットされる理由は、selectedItemPositionおよびentries属性の宣言順序のためです。私の例では、それらはその特定の順序で宣言され、自動生成されたバインディングコードは同じ順序で初期化を生成します。したがって

selectedItemPositionが正しくselectedItemPositionしたがって

0にリセットをArrayAdapterの entries原因のインスタンスの初期化を設定しても、修正は、レイアウトファイルに2つの属性の宣言を交換することです。

<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

私は最近、GitHubにデモアプリケーションを作成し、bindingAdapterとInverseBindingAdapterメカニズムを利用してスピナーで2ウェイデータバインディングを実現する方法を示しました。

このアプリでは、以下のスニペットに示すように、「android:selectedItemPosition」属性をバインドするのではなく、選択したアイテム(ObservableFieldクラスを利用する)をバインダーにバインダーしません。それは双方向バインディングであるため、スピナーアダプタのセットアップ中にバインドされたObservableField(すなわち、選択されたアイテム)に初期値を割り当てることによって、スピナーのbindingAdapter内の特別な処理と共に、スピナーの初期選択を達成することができる。

詳しくはデモアプリhereをご確認ください。

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> 

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

これは双方向バインディングで、' notifyPropertyChanged() '呼び出し? – Chisko

+0

onItemSelected()内の "inverseBindingListener_.onChange()"節は、フレームワークによって呼び出されるInverseBindingAdapter関数をトリガします。 InverseBindingAdapter関数は現在のトピックから外されているので、上のコードスニペットには表示されません。 – chrisli

+0

あなたは 'Observable'から' BaseObservable'に切り替えるだけで、現在の仕組みで定型文から自分自身を救うことができますか? – Chisko

関連する問題