最近在專案中用到ViewPager+FragmentPagerAdapter的方式來做介面,其中當adapter的資料來源資料更新時,呼叫adapter.notifyDataSetChanged()更新資料,發現ViewPager並沒有更新,還是原來的資料。
參考了別人的文章以及部分解決的的方法,加上自己的理解,拿出了下面這套解決方案。
目錄:
- 問題展示
- 解決方案
- 問題追究
問題展示
國際慣例,先上問題圖(圖一)以及正常圖(圖二)。
解決方案
- 在資料來源更新的前面加入以下程式碼
if (viewPager.getAdapter() != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
List<Fragment> fragments = fm.getFragments();
if(fragments != null && fragments.size() >0){
for (int i = 0; i < fragments.size(); i++) {
ft.remove(fragments.get(i));
}
}
ft.commit();}
複製程式碼
- 在你的adapter類中加入以下程式碼
private int mChildCount = 0;
@Overridepublic void notifyDataSetChanged() {
// 重寫這個方法,取到子Fragment的數量,用於下面的判斷,以執行多少次重新整理
mChildCount = getCount();
super.notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if ( mChildCount > 0) {
// 這裡利用判斷執行若干次不快取,重新整理
mChildCount --;
// 返回這個是強制ViewPager不快取,每次滑動都重新整理檢視
return POSITION_NONE;
}
// 這個則是快取不重新整理檢視
return super.getItemPosition(object);}
複製程式碼
較完整程式碼一覽
初始化資料
viewPager= (ViewPager) findViewById(R.id.pager);
mList=new ArrayList<Fragment>();
for (int i=1;i<4;i++){
Bundle bundle=new Bundle();
bundle.putString("text","第"+i+"頁");
MyFragment myFragment=new MyFragment();
myFragment.setArguments(bundle);
mList.add(myFragment);
}
adapter=new MyAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
複製程式碼
自定義adapter
public class MyAdapter extends FragmentPagerAdapter{
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override public Fragment getItem(int position) {
return mList.get(position);
}
@Override public int getCount() {
return mList.size();
}
// start
// 可以刪除這段程式碼看看,資料來源更新而viewpager不更新的情況
private int mChildCount = 0;
@Override public void notifyDataSetChanged() {
// 重寫這個方法,取到子Fragment的數量,用於下面的判斷,以執行多少次重新整理
mChildCount = getCount();
super.notifyDataSetChanged();
}
@Override public int getItemPosition(Object object) {
if ( mChildCount > 0) {
// 這裡利用判斷執行若干次不快取,重新整理
mChildCount --;
// 返回這個是強制ViewPager不快取,每次滑動都重新整理檢視
return POSITION_NONE;
}
// 這個則是快取不重新整理檢視
return super.getItemPosition(object);
}
// end
}
複製程式碼
更新資料來源的方法
public void update(){
// start
// 可以刪除這段程式碼看看,資料來源更新而viewpager不更新的情況
// 在資料來源更新前增加的程式碼,將上一次資料來源的fragment物件從FragmentManager中刪除
if (viewPager.getAdapter() != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
List<Fragment> fragments = fm.getFragments();
if(fragments != null && fragments.size() >0){
for (int i = 0; i < fragments.size(); i++) {
ft.remove(fragments.get(i));
}
}
ft.commit();
}
// End
mList.clear();
for (int i=4;i<7;i++){
Bundle bundle=new Bundle();
bundle.putString("text","第"+i+"頁");
MyFragment myFragment=new MyFragment();
myFragment.setArguments(bundle);
mList.add(myFragment);
}
// 重寫adapter的notifyDataChanged方法
adapter.notifyDataSetChanged();}
複製程式碼
demo原始碼下載
前往github下載原始碼 可根據註釋刪除對應的程式碼,體驗有問題以及正常的情況。
問題追究
首先來理解兩個adapter,都是繼承與pageradapter
- FragmentPagerAdapter:該類更專注於每一頁均為 Fragment 的情況。該類內的每一個生成的 Fragment 都將儲存在記憶體之中,因此適用於那些相對靜態的頁,數量也比較少的那種;如果需要處理有很多頁,並且資料動態性較大、佔用記憶體較多的情況,應該使用
FragmentStatePagerAdapter
。 - FragmentStatePagerAdapter:和
FragmentPagerAdapter
不一樣的是,正如其類名中的 'State' 所表明的含義一樣,該 PagerAdapter 的實現將只保留當前頁面,當頁面離開視線後,就會被消除,釋放其資源;而在頁面需要顯示時,生成新的頁面(就像 ListView 的實現一樣)。這麼實現的好處就是當擁有大量的頁面時,不必在記憶體中佔用大量的記憶體。 - 這兩個adapter最大的不同在於
instantiateItem()
這個方法
接下來看看adapter裡面getItemPosition這個方法
可以返回的值為POSITION_UNCHANGED
和POSITION_NONE
這兩個值。
而預設都是返回POSITION_UNCHANGED
這個返回值會在adapter的instantiateItem()
方法裡進行判斷:
POSITION_UNCHANGED
不重新載入item
POSITION_NONE
要求重新載入item
而網上的一些解決方案是直接複寫FragmentPagerAdapter
的getItemPosition
返回POSITION_NONE
,這樣做及違反了FragmentPagerAdapter
的設計原則(儲存在記憶體,載入更快等)也沒有解決今天這個坑,一樣是介面沒有重新整理的。
繼續說下去
假如返回POSITION_NONE
要求從新載入Item,ViewPager會首先去FragmentManager
裡面去查詢有沒有相關的fragment
如果有就直接使用如果沒有才會觸發FragmentPageadApter
的getItem
方法獲取一個fragment
。所以你更新的fragmentList集合是沒有作用的,還要清除FragmentManager
裡面快取的fragment
。
這樣今天的解決方案思路救出來了:
- 複寫
notifyDataSetChanged
@Override
public void notifyDataSetChanged() {
// 重寫這個方法,取到子Fragment的數量,用於下面的判斷,以執行多少次重新整理
mChildCount = getCount();
super.notifyDataSetChanged();
}
複製程式碼
- 複寫
getItemPosition
,根據mChildCount
判斷是返回POSITION_UNCHANGED
還是itemPOSITION_NONE
@Override
public int getItemPosition(Object object) {
if ( mChildCount > 0) {
// 這裡利用判斷執行若干次不快取,重新整理
mChildCount --;
// 返回這個是itemPOSITION_NONE
return POSITION_NONE;
}
// 這個則是POSITION_UNCHANGED
return super.getItemPosition(object);}
複製程式碼
- 在
notifyDataSetChanged
之前對FragmentManager
進行相應的刪除操作。
if (viewPager.getAdapter() != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
List<Fragment> fragments = fm.getFragments();
if(fragments != null && fragments.size() >0){
for (int i = 0; i < fragments.size(); i++) {
ft.remove(fragments.get(i));
}
}
ft.commit();}
複製程式碼
- 這樣就會在
notifyDataSetChanged
的時候重新整理檢視,在平時滑動等情況使用快取檢視,既保留了FragmentPagerAdapter
的特點,又解決了今天的坑。
到此,今天的坑又總算是跨過去了,如果有幫組到你,歡迎關注我的部落格和github 原始碼下載 本文參考自:
- http://www.cnblogs.com/lianghui66/p/3607091.html
- http://blog.sina.com.cn/s/blog_783ede03010173b4.html
2016年9月7日 00:35:49