转载自:http://blog.csdn.net/huachao1001/article/details/51658334
我们知道,ViewPager显示的页面离不开我们定义的适配器,正是因为我们编写了自己的适配器,才让ViewPager显示出满足你的需求的内容,那么ViewPager是如何与适配器(PagerAdapter)进行交互的呢?我们今天来研读一下ViewPager中与PagerAdapter交互的部分代码。本文对学习ViewPager很重要,请耐心往下仔细研读 …O(∩_∩)O~~
在分析源码之前,我们先看看ViewPager的最简单的示例用法,当然了,你也可以把你的所有View保存到一个List中。
通过用法找到切入点,ViewPager的比较典型的示例用法如下:
public class MainActivity extends AppCompatActivity {
private List<String> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
ViewPager viewPager = (ViewPager) findViewById(R.id.vp);
viewPager.setAdapter(new MyAdapter());
}
private void init() {
data = new ArrayList<>();
for (int i = 0; i < 10; i++) {
data.add("str" + i);
}
}
class MyAdapter extends PagerAdapter {
@Override
public int getCount() {
return data.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
TextView textView = new TextView(MainActivity.this);
String s = data.get(position);
textView.setText(s);
container.addView(textView);
return textView;
}
}
}
- 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
可以看到ViewPager
与我们的数据源之间是需要通过适配器来适配的。接下来我们去看看ViewPager
源码中,是如何与PagerAdapter
交互。
从setAdapter切入
上面简单示例代码中可用看到,调用ViewPager
的setAdapter
函数即可将ViewPager
与PagerAdapter
关联起来,我们先去查看ViewPager
的setAdapter
方法。
public void setAdapter(PagerAdapter adapter) {
if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
populate();
} else {
requestLayout();
}
}
if (mAdapterChangeListener != null && oldAdapter != adapter) {
mAdapterChangeListener.onAdapterChanged(oldAdapter, 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
从第2条注解中看到,需要清除观察者,另外从第14条注释中看到,需要设定观察者,那么这个观察者是干嘛的呢?显然,这里是通过使用观察者模式。就是说,当我们编写代码时,如果数据源发生变化,需要在代码里调用PagerAdapter的notifyDataSetChanged函数,即通知ViewPager数据源发生变化,ViewPager就是一个观察者,通过观察者类PagerObserver做相关应对操作。
另外,前面多次提到页面的抽象描述类ItemInfo
,我们看看ItemInfo
的定义:
static class ItemInfo {
Object object;
int position;
boolean scrolling;
float widthFactor;
float offset;
}
最后在第22条注释中,调用了populate()
函数,而populate()
函数是做什么的呢?可以说,我们在使用ViewPager
之所以流畅不卡,绝大部分功劳属于populate
函数。
大功臣populate函数
细心的童鞋会发现,早在上一篇文章《ViewPager源码分析(2):滑动及冲突处理 》的2.3 ViewPager 定义smoothScrollTo函数
小节源码中的第33行中,就出现过populate
函数,无参数的populate其内部是调用了有参的populate(int newCurrentItem)
函数,而newCurrentItem
表示当需要定位显示的页面。我们先看看源码:
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
if (mCurItem != newCurrentItem) {
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
sortChildDrawingOrder();
return;
}
if (mPopulatePending) {
if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
sortChildDrawingOrder();
return;
}
if (getWindowToken() == null) {
return;
}
mAdapter.startUpdate(this);
final int pageLimit = mOffscreenPageLimit;
final int startPos = Math.max(0, mCurItem - pageLimit);
final int N = mAdapter.getCount();
final int endPos = Math.min(N - 1, mCurItem + pageLimit);
if (N != mExpectedAdapterCount) {
String resName;
try {
resName = getResources().getResourceName(getId());
} catch (Resources.NotFoundException e) {
resName = Integer.toHexString(getId());
}
throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
" contents without calling PagerAdapter#notifyDataSetChanged!" +
" Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
" Pager id: " + resName +
" Pager class: " + getClass() +
" Problematic adapter: " + mAdapter.getClass());
}
int curIndex = -1;
ItemInfo curItem = null;
for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
final ItemInfo ii = mItems.get(curIndex);
if (ii.position >= mCurItem) {
if (ii.position == mCurItem) curItem = ii;
break;
}
}
if (curItem == null && N > 0) {
curItem = addNewItem(mCurItem, curIndex);
}
if (curItem != null) {
float extraWidthLeft = 0.f;
int itemIndex = curIndex - 1;
ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
final int clientWidth = getClientWidth();
final float leftWidthNeeded = clientWidth <= 0 ? 0 :
2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
for (int pos = mCurItem - 1; pos >= 0; pos--) {
if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
if (ii == null) {
break;
}
if (pos == ii.position && !ii.scrolling) {
mItems.remove(itemIndex);
mAdapter.destroyItem(this, pos, ii.object);
if (DEBUG) {
Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
" view: " + ((View) ii.object));
}
itemIndex--;
curIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
extraWidthLeft += ii.widthFactor;
itemIndex--;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex + 1);
extraWidthLeft += ii.widthFactor;
curIndex++;
ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
}
}
calculatePageOffsets(curItem, curIndex, oldCurInfo);
}
if (DEBUG) {
Log.i(TAG, "Current page list:");
for (int i = 0; i < mItems.size(); i++) {
Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
}
}
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
mAdapter.finishUpdate(this);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.childIndex = i;
if (!lp.isDecor && lp.widthFactor == 0.f) {
final ItemInfo ii = infoForChild(child);
if (ii != null) {
lp.widthFactor = ii.widthFactor;
lp.position = ii.position;
}
}
}
sortChildDrawingOrder();
if (hasFocus()) {
View currentFocused = findFocus();
ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
if (ii == null || ii.position != mCurItem) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
if (child.requestFocus(View.FOCUS_FORWARD)) {
break;
}
}
}
}
}
}
- 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
从populate函数源码我们看到,ViewPager缓存当前显示的页面左右两边的页面,这个页面个数默认为左右两边各1个(如果左右都有至少1个的话),或者是用户通过调用ViewPager的setOffscreenPageLimit(int limit)
函数来设定左右两边保持(好吧,原谅我从头到尾用缓存
这个词)的页面个数。看看setOffscreenPageLimit(int limit)
源码:
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
可以看到如果我们设置的值小于1,那么ViewPager会将缓存页面数量设置为1,即,缓存的页面数量至少为1,并且每次改变缓存数量后也会调用populate函数。
既然ViewPager
真正的子View
个数只是两边”缓存”的页面个数+1(当前显示的页面),那么ViewPager
是如何做到无阻碍的从头滑到尾而不出问题呢?前面我们提到,smoothScrollTo
函数里面也调用了populate
函数,而populate
函数维护了当前显示的页面和左右两边“缓存”的页面,这样就做到了能滑到结尾。那么populate
是如何让ViewPager
的子View
一直保持为两边”缓存”的页面+当前显示的页面呢?其实,源码上面很明显,populate
先判断页面是否不在缓存的范围内,如果不在缓存范围内,则Destroy
掉(调用PagerAdapter
的destroyItem
),而如果在缓存范围,但是这个位置上页面不存在(即没有加入到ViewPager
,作为ViewPager子View
),则调用PagerAdapter
的instantiateItem
来添加新页面(通过addNewItem
来调用)。假设我执行了setOffscreenPageLimit(2)
函数,那么我们看看ViewPager的简单示意图:
如果你仔细想想,你会发现,这原理跟《打造属于你的LayoutManager 》一文中的RecyclerView很像,不同的是,RecyclerView有回收利用,而ViewPager没有View的回收利用。