2012-09-20 14 views
21

getItemPosition(Object object)を並べ替える目的でFragmentStatePagerAdapterが正しく動作しないと思います。getItemPosition(オブジェクトオブジェクト)を使用してFragmentStatePagerAdapterのページを並べ替える

以下は簡単な例です。初期状態では、ページの順序は{A、B、C}です。 toggleState()を呼び出すと、ページの順序が{A、C、B}に変わります。 getItemPosition(Object object)を無効にすることで、表示中の現在のページ(A、B、またはC)が変更されないようにします。

public static class TestPagerAdapter extends FragmentStatePagerAdapter { 
    private boolean mState = true; 

    public TestPagerAdapter(FragmentManager fragmentManager) { 
     super(fragmentManager); 
    } 

    @Override 
    public int getCount() { 
     return 3; 
    } 

    private void toggleState() { 
     mState = !mState; 
     notifyDataSetChanged(); 
    } 

    private String getLabel(int position) { 
     switch (position) { 
      case 0: 
       return "A"; 
      case 1: 
       return mState ? "B" : "C"; 
      default: 
       return mState ? "C" : "B"; 
     } 
    } 

    @Override 
    public int getItemPosition(Object object) { 
     String label = ((TestFragment) object).getLabel(); 
     if (label.equals("A")) { 
      return 0; 
     } else if (label.equals("B")) { 
      return mState ? 1 : 2; 
     } else { 
      return mState ? 2 : 1; 
     } 
    } 

    @Override 
    public CharSequence getPageTitle(int position) { 
     return getLabel(position); 
    } 

    @Override 
    public Fragment getItem(int position) { 
     return TestFragment.newInstance(getLabel(position)); 
    } 
} 

不適切なように見える2つの動作が発生しました。

  1. (他のページにスワイプする前に、ページAを見ながら)toggleState()私はすぐに呼び出すと、アプリがクラッシュします。 FragmentStatePagerAdapterのソースを見ると、これは私が最初にスワイプするラインで

  2. を呼び出す前に、最初の(行113-115のように)mFragmentsの大きさを確認することで固定されるだろう

    java.lang.IndexOutOfBoundsException: Invalid index 2, size is 2 
        at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251) 
        at java.util.ArrayList.set(ArrayList.java:477) 
        at android.support.v4.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:136) 
        at android.support.v4.view.ViewPager.populate(ViewPager.java:867) 
        at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:469) 
        at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:441) 
        at android.support.v4.view.ViewPager.dataSetChanged(ViewPager.java:766) 
        at android.support.v4.view.ViewPager$PagerObserver.onChanged(ViewPager.java:2519) 
        at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37) 
        at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:276) 
        at com.ugglynoodle.test.testfragmentstatepageradapter.MainActivity$TestPagerAdapter.toggleState(MainActivity.java:55) 
        ... 
    

    ページBがある場合はgetItem(2)が呼び出され、ページCが作成され、mFragmentsのサイズが3になりました(これにより、上記のクラッシュが発生しないようになります)。その後、ページAにスワイプし、ページCは破棄されます(2ページ離れているため、デフォルトのオフスクリーンページの制限は1です)。今、toggleState()と呼んでいます。ページBが破棄されました。しかし、ページCは再作成されません!つまり、右にスワイプすると、空のページが表示されます。

まず、私は正しいかどうか、これらは実際にはバグか、何か間違っているかどうかを知ることができます。それらがバグであれば誰でも回避策を提案することができます(サポートライブラリを自分でデバッグして再構築する以外)確かに誰かが getItemPosition(Object object)をオーバーライドする必要があります(すべてを POSITION_NONEに設定する以外は)?

私はサポートライブラリの最新リビジョン(10)を使用しています。

答えて

33

FragmentStatePagerAdapterのソースを見ると、間違っていることがわかりました。 FragmentStatePagerAdapterは、フラグメントリストと保存された状態をArrayListにキャッシュします(mFragmentsおよびmSavedState)。しかし、フラグメントが並べ替えられたときには、mFragmentsmSavedStateの要素を並べ替える仕組みがありません。したがって、アダプタはページャに誤ったフラグメントを提供します。

私はこのためにan issueを提出し、固定実装(NewFragmentStatePagerAdapter.java)を問題に添付しました。この修正では、FragmentStatePagerAdapterにgetItemId()関数を追加しました。 (これは、FragmentPagerAdapterの並べ替えの実装を反映しています。)アダプタ位置によるitemIdの配列は、常に格納されます。次に、notifyDataSetChanged()で、アダプターはitemIds配列が変更されたかどうかをチェックします。それがある場合、mFragmentsmSavedStateがそれに応じて並べ替えられます。さらなる修飾は、destroyItem(),saveState()およびrestoreState()に見出すことができる。

このクラスを使用するには、getItemPosition()getItemId()を一貫してgetItem()で実装する必要があります。

+0

私はあなたの固定実装を使用していたが、私は直面しています一つの問題は、ViewPager.Howで隣接フラグメントのNO UPDATIONではありません私はそれを修正することはできますか? –

+1

あなたは@UgglyNoodleにいると思いますが、並べ替えられたフラグメントを破棄して再作成しても問題がない場合は、順序が変更されたフラグメントに対してはPOSITION_NONEを返し、変更されていないフラグメントについてはPOSTION_UNCHANGEDクラッシュすることなく所望の結果を達成する。 –

1

私にとっては、an issueの回答のうちの1つが働いていました。回答#20#21。ソリューションhttps://gist.github.com/ypresto/8c13cb88a0973d071a64にリンクしてください。 ベスト・ソリューション、ページの更新と並び替えの作業。他のソリューションの既知のバグであるitem(メソッドdestroyItem内)を破棄するときに、このソリューションでのみAdapterがIndexOutOfBoundsExeptionをスローしませんでした。

0

商品IDにlongの代わりにStringを返すことができるように、existing solutionをKotlinに再実装しました。あなたはhere以下それを見つけることができます。

import android.annotation.SuppressLint 
import android.os.Bundle 
import android.os.Parcelable 
import android.support.v4.app.Fragment 
import android.support.v4.app.FragmentManager 
import android.support.v4.app.FragmentTransaction 
import android.view.View 
import android.view.ViewGroup 
import java.util.HashSet 
import java.util.LinkedHashMap 

/** 
* A PagerAdapter that can withstand item reordering. See 
* https://issuetracker.google.com/issues/36956111. 
* 
* @see android.support.v4.app.FragmentStatePagerAdapter 
*/ 
abstract class MovableFragmentStatePagerAdapter(
     private val manager: FragmentManager 
) : NullablePagerAdapter() { 
    private var currentTransaction: FragmentTransaction? = null 
    private var currentPrimaryItem: Fragment? = null 

    private val savedStates = LinkedHashMap<String, Fragment.SavedState>() 
    private val fragmentsToItemIds = LinkedHashMap<Fragment, String>() 
    private val itemIdsToFragments = LinkedHashMap<String, Fragment>() 
    private val unusedRestoredFragments = HashSet<Fragment>() 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.getItem */ 
    abstract fun getItem(position: Int): Fragment 

    /** 
    * @return a unique identifier for the item at the given position. 
    */ 
    abstract fun getItemId(position: Int): String 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.startUpdate */ 
    override fun startUpdate(container: ViewGroup) { 
     check(container.id != View.NO_ID) { 
      "ViewPager with adapter $this requires a view id." 
     } 
    } 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.instantiateItem */ 
    override fun instantiateItem(container: ViewGroup, position: Int): Any { 
     val itemId = getItemId(position) 

     val f = itemIdsToFragments[itemId] 
     if (f != null) { 
      unusedRestoredFragments.remove(f) 
      return f 
     } 

     if (currentTransaction == null) { 
      // We commit the transaction later 
      @SuppressLint("CommitTransaction") 
      currentTransaction = manager.beginTransaction() 
     } 

     val fragment = getItem(position) 
     fragmentsToItemIds.put(fragment, itemId) 
     itemIdsToFragments.put(itemId, fragment) 

     val fss = savedStates[itemId] 
     if (fss != null) { 
      fragment.setInitialSavedState(fss) 
     } 
     fragment.setMenuVisibility(false) 
     fragment.userVisibleHint = false 

     currentTransaction!!.add(container.id, fragment) 

     return fragment 
    } 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.destroyItem */ 
    override fun destroyItem(container: ViewGroup, position: Int, fragment: Any) { 
     (fragment as Fragment).destroy() 
    } 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.setPrimaryItem */ 
    override fun setPrimaryItem(container: ViewGroup, position: Int, fragment: Any?) { 
     fragment as Fragment? 
     if (fragment !== currentPrimaryItem) { 
      currentPrimaryItem?.let { 
       it.setMenuVisibility(false) 
       it.userVisibleHint = false 
      } 

      fragment?.setMenuVisibility(true) 
      fragment?.userVisibleHint = true 
      currentPrimaryItem = fragment 
     } 
    } 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.finishUpdate */ 
    override fun finishUpdate(container: ViewGroup) { 
     if (!unusedRestoredFragments.isEmpty()) { 
      for (fragment in unusedRestoredFragments) fragment.destroy() 
      unusedRestoredFragments.clear() 
     } 

     currentTransaction?.let { 
      it.commitAllowingStateLoss() 
      currentTransaction = null 
      manager.executePendingTransactions() 
     } 
    } 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.isViewFromObject */ 
    override fun isViewFromObject(view: View, fragment: Any): Boolean = 
      (fragment as Fragment).view === view 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.saveState */ 
    override fun saveState(): Parcelable? = Bundle().apply { 
     putStringArrayList(KEY_FRAGMENT_IDS, ArrayList<String>(savedStates.keys)) 
     putParcelableArrayList(
       KEY_FRAGMENT_STATES, 
       ArrayList<Fragment.SavedState>(savedStates.values) 
     ) 

     for ((f, id) in fragmentsToItemIds.entries) { 
      if (f.isAdded) { 
       manager.putFragment(this, "$KEY_FRAGMENT_STATE$id", f) 
      } 
     } 
    } 

    /** @see android.support.v4.app.FragmentStatePagerAdapter.restoreState */ 
    override fun restoreState(state: Parcelable?, loader: ClassLoader?) { 
     if ((state as Bundle?)?.apply { classLoader = loader }?.isEmpty == false) { 
      state!! 

      fragmentsToItemIds.clear() 
      itemIdsToFragments.clear() 
      unusedRestoredFragments.clear() 
      savedStates.clear() 

      val fragmentIds: List<String> = state.getStringArrayList(KEY_FRAGMENT_IDS) 
      val fragmentStates: List<Fragment.SavedState> = 
        state.getParcelableArrayList(KEY_FRAGMENT_STATES) 

      for ((index, id) in fragmentIds.withIndex()) { 
       savedStates.put(id, fragmentStates[index]) 
      } 

      for (key: String in state.keySet()) { 
       if (key.startsWith(KEY_FRAGMENT_STATE)) { 
        val itemId = key.substring(KEY_FRAGMENT_STATE.length) 

        manager.getFragment(state, key)?.let { 
         it.setMenuVisibility(false) 
         fragmentsToItemIds.put(it, itemId) 
         itemIdsToFragments.put(itemId, it) 
        } 
       } 
      } 

      unusedRestoredFragments.addAll(fragmentsToItemIds.keys) 
     } 
    } 

    private fun Fragment.destroy() { 
     if (currentTransaction == null) { 
      // We commit the transaction later 
      @SuppressLint("CommitTransaction") 
      currentTransaction = manager.beginTransaction() 
     } 

     val itemId = fragmentsToItemIds.remove(this) 
     itemIdsToFragments.remove(itemId) 
     if (itemId != null) { 
      savedStates.put(itemId, manager.saveFragmentInstanceState(this)) 
     } 

     currentTransaction!!.remove(this) 
    } 

    private companion object { 
     const val KEY_FRAGMENT_IDS = "fragment_keys_" 
     const val KEY_FRAGMENT_STATES = "fragment_states_" 
     const val KEY_FRAGMENT_STATE = "fragment_state_" 
    } 
} 

とJavaピース:

import android.support.annotation.NonNull; 
import android.support.annotation.Nullable; 
import android.support.v4.view.PagerAdapter; 
import android.view.ViewGroup; 

/** 
* A PagerAdapter whose {@link #setPrimaryItem} is overridden with proper nullability annotations. 
*/ 
public abstract class NullablePagerAdapter extends PagerAdapter { 
    @Override 
    public void setPrimaryItem(@NonNull ViewGroup container, 
           int position, 
           @Nullable Object object) { 
     // `object` is actually nullable. It's even in the dang source code which is hilariously 
     // ridiculous: 
     // `mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);` 
    } 
} 
関連する問題