Android修改FragmentStatePagerAdapter源码实现ViewPager+Fragment左右无限滑动功能

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
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 ArrayList<Fragment.SavedState> mSavedState = new ArrayList();
// private ArrayList<Fragment> mFragments = new ArrayList();
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);
}
}

// while(this.mFragments.size() <= position) {
// this.mFragments.add(null);
// }

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

// while(this.mSavedState.size() <= position) {
// this.mSavedState.add(null);
// }

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();
// Fragment.SavedState[] i = new Fragment.SavedState[this.mSavedState.size()];
// this.mSavedState.toArray(i);
// this.mSavedState.values().toArray(i);
Iterator<Integer> it = this.mSavedState.keySet().iterator();
while(it.hasNext()){
int i = it.next();
state.putParcelable("s"+i,this.mSavedState.get(i));
}
// state.putParcelableArray("states", 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);
// Parcelable[] fss = bundle.getParcelableArray("states");
this.mSavedState.clear();
this.mFragments.clear();
// if(fss != null) {
// for(int keys = 0; keys < fss.length; ++keys) {
// this.mSavedState.add((Fragment.SavedState)fss[keys]);
// }
// }

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) {
// while(this.mFragments.size() <= index) {
// this.mFragments.add(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中将我们保存的数据恢复再显示就可以了。

坚持原创技术分享,您的支持将鼓励我继续创作!