2

我在FragmentPagerAdapter中有〜20個片段,其中包含一個列表,它從中選取片段,因此不會重新創建片段。FragmentPagerAdapter中的Android更改片段順序

private List<TitledFragment> fragments; 

public SectionsPagerAdapter(FragmentManager fm) { 
    super(fm); 

    this.fragments = new ArrayList<TitledFragment>(); 
} 

@Override 
public Fragment getItem(int i) { 
    return fragments.get(i).getFragment(); 
} 

@Override 
public int getCount() { 
    return fragments.size(); 
} 

@Override 
public CharSequence getPageTitle(int position) { 
    return fragments.get(position).getTitle(); 
} 

public synchronized List<TitledFragment> getFragments() { 
    return fragments; 
} 


public synchronized void addFragment(TitledFragment fragment) { 
    fragments.add(fragment); 
    notifyDataSetChanged(); 
} 

的FragmentPagerAdapter設置爲ViewPager的適配器之後,我重新排列片段(洗牌只是爲了測試)

Collections.shuffle(mSectionsPagerAdapter.getFragments()); 
mSectionsPagerAdapter.notifyDataSetChanged(); 

出於某種原因,只有標題的順序被改變,即使它爲getItem返回不同的片段,因爲順序不同。這是爲什麼,我該如何解決它?

回答

8

您還需要覆蓋getItemPosition()。默認情況下,此函數始終返回POSITION_UNCHANGED,因此當您調用notifyDataSetChanged()時,適配器會假定所有片段仍處於相同位置。 (它不會再次調用getItem(),因爲它認爲所有必需的片段都已創建。)新的getItemPosition()應該在混洗之後返回每個片段的新位置。

一個簡單的攻擊是總是返回POSITION_NONE,然後適配器將放棄所有現有的碎片,並在調用getItem()時將它們重新創建到正確的位置。當然,這不是有效的,並且具有額外的缺點,即正在查看的當前片段可能會改變。但是,由於重新排序片段可能存在一些錯誤 - 請參閱my question here - POSITION_NONE方法可能是最安全的方法。

+0

我可以證實這是一個很好的解決方案!謝謝! –

0

接受的答案是非最佳答案。從int getItemPosition(Object)退回POSITION_NONE只是破壞了有效片段管理的任何希望,要求所有片段被重新實例化。它也忽略了另一個問題。 FragmentPageAdapter將Fragment的緩存副本保存在FragmentManager中,並在實例化新的Fragment時查找這些副本。如果它發現它認爲是匹配的片段,則不會調用public Fragment getItem(int)方法,並使用緩存的副本。

例如,假設頁面0和頁面1已加載,則在FragmentManager中將會有標記爲0和1的緩存片段。現在在索引0處插入頁面(不要忘記撥打notifyDataSetChanged()),舊的索引0變爲1,1變爲2(這是使用方法public int FragmentPageAdapter.getItemPosition(Object)的方法發出的)。對於項目返回0 POSITION_NONE(因爲它是新的位置),因此該方法public Object instantiateItem(ViewGroup, int)被調用位置0:

public Object instantiateItem(ViewGroup container, int position) { 
    if (mCurTransaction == null) { 
     mCurTransaction = mFragmentManager.beginTransaction(); 
    } 

    final long itemId = getItemId(position); 

    // Do we already have this fragment? 
    String name = makeFragmentName(container.getId(), itemId); 
    Fragment fragment = mFragmentManager.findFragmentByTag(name); 
    if (fragment != null) { 
     if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); 
     mCurTransaction.attach(fragment); 
    } else { 
     fragment = getItem(position); 
     ... 

看看會發生什麼,一個緩存片段被發現的位置0,和你想要的片段在位置1現在位於位置0,您想要在位置2的片段現在位於位置1,在位置2您獲得由FragmentPageAdapter.getItem(int)返回的新位置,該位置是位置1的重複。

如何解決這個問題?我見過很多建議,對SO包括:

  1. 始終從FragmentPageAdapter.getItemPosition()返回https://stackoverflow.com/a/7386616/2351246 POSITION_NONE - 這個答案忽略efficiciency和內存管理(最終將沒有清理掉緩存片段工作)
  2. 跟蹤片段標籤https://stackoverflow.com/a/12104399/2351246,這依賴於可能改變的實現細節。
  3. 而最糟糕的是通過反向工程FragmentPageAdapter實現https://stackoverflow.com/a/13925130/2351246來使用魔法,這太可怕了。

沒有必要破壞內存管理或跟蹤FragmentPagerAdapter的內部實現細節。在所有的答案缺少的細節是,想要到重新排序片段一個FragmentPagerAdapter必須實現的方法public long getItemId(int position)也:

@Override 
    public long getItemId(int position) { 
     return System.identityHashCode(fragments.get(position)); 
    } 

這提供了可用於查找正確的片段在非基於位置的ID FragmentManager緩存即使移動頁面。

相關問題