Android中使用Viewpager+Fragment很简单就能实现一个左右滑动翻页的功能,但是只能实现固定的页数的左右滑动。为了实现左右无限滑动的功能想到了可以给adapter的getCount()返回一个足够大的数值,比如Integer.MAX_VALUE。于是写了代码测试一下: adapter继承至FragmentStatePagerAdapter,因为页面会很多,所以使用FragmentStatePagerAdapter只会缓存前一页、当前页、上一页三页的界面,将其他未显示的页面回收,但是会缓存已回收的Fragment的状态SavedState,可以在自定义的Fragment里重写onSaveInstanceState方法将数据保存到SavedState里,然后在onActivityCreated里进行数据恢复。设置adapte的getCount()返回值为Integer.MAX_VALUE,在Activity里设置Viewpager当前显示页为mViewPager.setCurrentItem(Integer.MAX_VALUE/2);中间位置,这样就可以左右无限滑了。
测试运行了一下发现会报错,错误如下:
1 2 3 4 5 6 7 java.lang.OutOfMemoryError at java.util.ArrayList.add(ArrayList.java:118 ) at android.support.v4.app.FragmentStatePagerAdapter.instantiateItem(FragmentStatePagerAdapter.java:114 ) at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:832 ) at android.support.v4.view.ViewPager.populate(ViewPager.java:982 ) at android.support.v4.view.ViewPager.populate(ViewPager.java:914 ) at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1436 )
错误很明显OOM,看错误提示是在FragmentStatePagerAdapter里的ArrayList引起的,打开FragmentStatePagerAdapter源码查看一下发现有两个成员变量:
1 2 private ArrayList<SavedState> mSavedState = new ArrayList ();private ArrayList<Fragment> mFragments = new ArrayList ();
用来保存所有Fragment的SavedState状态和所有的Fragment实例。 在看instantiateItem跟destroyItem里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public Object instantiateItem (ViewGroup container, int position) { Fragment fragment; if (this .mFragments.size() > position) { fragment = (Fragment)this .mFragments.get(position); if (fragment != null ) { return fragment; } } if (this .mCurTransaction == null ) { this .mCurTransaction = this .mFragmentManager.beginTransaction(); } fragment = this .getItem(position); if (this .mSavedState.size() > position) { SavedState fss = (SavedState)this .mSavedState.get(position); if (fss != null ) { fragment.setInitialSavedState(fss); } } while (this .mFragments.size() <= position) { this .mFragments.add((Object)null ); } fragment.setMenuVisibility(false ); fragment.setUserVisibleHint(false ); this .mFragments.set(position, fragment); this .mCurTransaction.add(container.getId(), fragment); return fragment; } public void destroyItem (ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (this .mCurTransaction == null ) { this .mCurTransaction = this .mFragmentManager.beginTransaction(); } while (this .mSavedState.size() <= position) { this .mSavedState.add((Object)null ); } this .mSavedState.set(position, this .mFragmentManager.saveFragmentInstanceState(fragment)); this .mFragments.set(position, (Object)null ); this .mCurTransaction.remove(fragment); }
发现在instantiateItem跟destroyItem有两个while循环
1 2 3 4 5 6 7 while (this .mFragments.size() <= position) { this .mFragments.add((Object)null ); } while (this .mSavedState.size() <= position) { this .mSavedState.add((Object)null ); }
因为我给adapter的getCount()返回了Integer.MAX_VALUE值,所有势必会造成position的值特别大,所以上面这两个循环就会出现OOM现象。所以我想着修改FragmentStatePagerAdapter源码将ArrayList改成Map只保存已滑动过的Fragment及其SavedState。复制FragmentStatePagerAdapter源码到一个我自己新建的CustomFragmentStatePagerAdapter的自定义adapter文件里。修改后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 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.support.v4.view.PagerAdapter;import android.util.Log;import android.view.View;import android.view.ViewGroup;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter { private static final String TAG = "FragmentStatePagerAdapter" ; private static final boolean DEBUG = false ; private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null ; private Map<Integer,Fragment.SavedState> mSavedState = new HashMap <>(); private Map<Integer,Fragment> mFragments = new HashMap <>(); private Fragment mCurrentPrimaryItem = null ; public CustomFragmentStatePagerAdapter (FragmentManager fm) { this .mFragmentManager = fm; } public abstract Fragment getItem (int var1) ; @Override public void startUpdate (ViewGroup container) { } public Fragment getFragment (int position) { return this .mFragments.get(position); } @Override public Object instantiateItem (ViewGroup container, int position) { Fragment fragment; if (this .mFragments.containsKey(position)) { fragment = this .mFragments.get(position); if (fragment != null ) { return fragment; } } if (this .mCurTransaction == null ) { this .mCurTransaction = this .mFragmentManager.beginTransaction(); } fragment = this .getItem(position); if (this .mSavedState.containsKey(position)) { Fragment.SavedState fss = this .mSavedState.get(position); if (fss != null ) { fragment.setInitialSavedState(fss); } } fragment.setMenuVisibility(false ); fragment.setUserVisibleHint(false ); this .mFragments.put(position, fragment); this .mCurTransaction.add(container.getId(), fragment); return fragment; } @Override public void destroyItem (ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (this .mCurTransaction == null ) { this .mCurTransaction = this .mFragmentManager.beginTransaction(); } this .mSavedState.put(position, this .mFragmentManager.saveFragmentInstanceState(fragment)); this .mFragments.put(position, null ); this .mCurTransaction.remove(fragment); } @Override public void setPrimaryItem (ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (fragment != this .mCurrentPrimaryItem) { if (this .mCurrentPrimaryItem != null ) { this .mCurrentPrimaryItem.setMenuVisibility(false ); this .mCurrentPrimaryItem.setUserVisibleHint(false ); } if (fragment != null ) { fragment.setMenuVisibility(true ); fragment.setUserVisibleHint(true ); } this .mCurrentPrimaryItem = fragment; } } @Override public void finishUpdate (ViewGroup container) { if (this .mCurTransaction != null ) { this .mCurTransaction.commitAllowingStateLoss(); this .mCurTransaction = null ; this .mFragmentManager.executePendingTransactions(); } } @Override public boolean isViewFromObject (View view, Object object) { return ((Fragment)object).getView() == view; } @Override public Parcelable saveState () { Bundle state = null ; if (this .mSavedState.size() > 0 ) { state = new Bundle (); Iterator<Integer> it = this .mSavedState.keySet().iterator(); while (it.hasNext()){ int i = it.next(); state.putParcelable("s" +i,this .mSavedState.get(i)); } } Iterator<Integer> fit = this .mFragments.keySet().iterator(); while (fit.hasNext()) { int var5 = fit.next(); Fragment f = this .mFragments.get(var5); if (f != null ) { if (state == null ) { state = new Bundle (); } String key = "f" + var5; this .mFragmentManager.putFragment(state, key, f); } } return state; } @Override public void restoreState (Parcelable state, ClassLoader loader) { if (state != null ) { Bundle bundle = (Bundle)state; bundle.setClassLoader(loader); this .mSavedState.clear(); this .mFragments.clear(); Iterator<String> si = bundle.keySet().iterator(); while (si.hasNext()){ String skey = si.next(); if (skey.startsWith("s" )){ int p = Integer.parseInt(skey.substring(1 )); this .mSavedState.put(p, (Fragment.SavedState) bundle.getParcelable(skey)); } } Set var10 = bundle.keySet(); Iterator i$ = var10.iterator(); while (true ) { while (true ) { String key ; do { if (!i$.hasNext()) { return ; } key = (String)i$.next(); } while (!key.startsWith("f" )); int index = Integer.parseInt(key.substring(1 )); Fragment f = this .mFragmentManager.getFragment(bundle, key); if (f != null ) { f.setMenuVisibility(false ); this .mFragments.put(index, f); } else { Log.w("CustomFragmentStatePagerAdapter" , "Bad fragment at key " + key); } } } } } }
主要修改了instantiateItem和destroyItem中以及saveState和restoreState中将原来操作List的代码改成了操作Map。 OK修改好以后将我们自定义的adapter改为继承我们刚才写的CustomFragmentStatePagerAdapter,然后重新运行,发现已经实现了左右滑动的功能,而且能一直滑动下去,已经加载过的Fragment的数据因为已经缓存过了所以重新加载界面的时候不需要再重新加载数据了,只需要从SavedState中将我们保存的数据恢复再显示就可以了。