http://blog.csdn.net/zzxzhyt/article/details/50689308
版本
Android.support.v4.view.ViewPager
android.support.v4.view. PagerAdapter
这一篇主要是针对ViewPager的深度解析,本篇文章就为了解决两个疑问:ViewPager是怎么实现的?ViewPager与PagerAdapter又是怎么交互的?我们会一一揭开ViewPager的面纱,借此对ViewPager有更深层次的了解,而不仅仅停留在使用层面上。本篇通过以下三个问题来剖析ViewPager源码:
- ViewPager怎么创建视图?
- ViewPager怎么实现滚动?
- PagerAdapter与ViewPager怎么交互的?我们创建适配器时候重写的方法对ViewPager有什么影响?
如果你有接触过自定义View的话,那么你会知道我们通常都是通过重写View.onMeasure(),View.onLayout(),View.onDraw()。去自定义View.所以我们通过这三个方法去一一剖析。
在分析解析onMeasure之前我们要先明白一点,ViewPager的Child View分为两种,一种是Decor View
装饰视图,一种是Content View,也就是我们通过适配器给予的内容视图。
- onMeasure(int widthMeasureSpec, int heightMeasureSpec)
ViewPager.onMeasure(int,int)做了三件事:
- 设置ViewPager的测量长宽值为父容器给与的最大长宽值
- 测量Decor View
- 测量Content View
- 如果存在适配器且有内容,则更新Content View
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); /**. 省略Code..**/ int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(); int childHeightSize = getMeasuredHeight() - getPaddingTop() -getPaddingBottom(); int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); /**..省略Code..***/ final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); child.measure(widthSpec, heightSpec); if (consumeVertical) { childHeightSize -= child.getMeasuredHeight(); } else if (consumeHorizontal) { childWidthSize -= child.getMeasuredWidth(); } } } } mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); mInLayout = true; populate(); mInLayout = false; size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp == null || !lp.isDecor) { final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY); child.measure(widthSpec, mChildHeightMeasureSpec); } } } }
- 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
- 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
上面我们提到了populate()方法。这个方法主要是更新&创建&销毁相应的Content View,这个方法在源码中多次被使用,这个方法也确实起着十分重要的作用,我们看看它的源码。
void populate() { populate(mCurItem); } void populate(int newCurrentItem) { ItemInfo oldCurInfo = null; int focusDirection = View.FOCUS_FORWARD; if (mCurItem != newCurrentItem) { focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT; oldCurInfo = infoForPosition(mCurItem); mCurItem = newCurrentItem; } /**..省略Code...,这里是一系列针对强壮性的判断,自行查看源码**/ 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); /**..省略Code...,这里是一系列针对强壮性的判断,自行查看源码**/ 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) { /** ..省略Code.. **/ for (int pos = mCurItem - 1; pos >= 0; pos--) { if (extraWidthLeft >= leftWidthNeeded && pos < startPos) { /** ..省略Code..,根据页面最大持有数创建和销毁左边页面Content View **/ } } float extraWidthRight = curItem.widthFactor; itemIndex = curIndex + 1; if (extraWidthRight < 2.f) { ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null; final float rightWidthNeeded = clientWidth <= 0 ? 0 : (float) getPaddingRight() / (float) clientWidth + 2.f; for (int pos = mCurItem + 1; pos < N; pos++) { /** ..省略Code..,根据页面最大持有数创建和销毁右边页面Content View **/ } } calculatePageOffsets(curItem, curIndex, oldCurInfo); } mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); mAdapter.finishUpdate(this); /**..省略Code..,更新所有 Content View的Layout Params信息**/ }
- 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
- 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
这里我们总结一下populate(int)做的事情:
1.如果还未有当前页面的Content View,则通过PagerAdapter创建 Content View。
2.根据页面数量极限值,即ViewPager.mOffscreenPageLimit.来创建或者销毁页面左右的两边的页面。
3.更新所有Content View 的LayoutParams属性
- onLayout(boolean changed, int l, int t, int r, int b)
结束了populate(int)这个小插曲之后,接下来我们分析 onLayout()。在onLayout过程中,主要是处理Decor View 与Content View的位置,由于Decor View 可能有多个,所以这里是根据类似线性布局的方式对Decor View进行布局。例如从上到下等,Content View 则在减去了Decor View占用空间的基础上,从左到右进行布局。
protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); int width = r - l; int height = b - t; int paddingLeft = getPaddingLeft(); int paddingTop = getPaddingTop(); int paddingRight = getPaddingRight(); int paddingBottom = getPaddingBottom(); final int scrollX = getScrollX(); int decorCount = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int childLeft = 0; int childTop = 0; if (lp.isDecor) { /**省略Code,根据装饰视图的布局进行相应处理**/ } childLeft += scrollX; child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); decorCount++; } } } final int childWidth = width - paddingLeft - paddingRight; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ItemInfo ii; if (!lp.isDecor && (ii = infoForChild(child)) != null) { int loff = (int) (childWidth * ii.offset); int childLeft = paddingLeft + loff; int childTop = paddingTop; if (lp.needsMeasure) { lp.needsMeasure = false; final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidth * lp.widthFactor), MeasureSpec.EXACTLY); final int heightSpec = MeasureSpec.makeMeasureSpec( (int) (height - paddingTop - paddingBottom), MeasureSpec.EXACTLY); child.measure(widthSpec, heightSpec); } child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } /***省略Code,保存padding值**/ if (mFirstLayout) { scrollToItem(mCurItem, false, 0, false); } }
- 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
- 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
onLayout()方法十分清晰,主要是对Decor View 与Content View的布局。
接下来我们看绘画阶段,ViewPager的重写了draw(Canvas)与 onDraw(Canvas)。draw(Canvas)方法很简单,只是对左右边缘效果进行处理,而onDraw(Canvas)亦十分简单,只是绘画了页面的间隔效果。
- public void draw(Canvas canvas)
public void draw(Canvas canvas) { super.draw(canvas); boolean needsInvalidate = false; final int overScrollMode = ViewCompat.getOverScrollMode(this); if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && mAdapter != null && mAdapter.getCount() > 1)) { if (!mLeftEdge.isFinished()) { /**省略Code,这里绘画左边缘效果**/ } if (!mRightEdge.isFinished()) { /**省略Code,这里绘画右边缘效果**/ } } else { mLeftEdge.finish(); mRightEdge.finish(); } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- public void onDraw(Canvas)
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) { /**省略Code,根据注释这里绘画了页面的间隔效果**/ } } }
至此,ViewPager的测量、布局、绘画三个过程均已经解析完毕。
在这三个过程中,不知道大家在阅读源码的时候会不会对Child View的来源感产生疑问?这里我特别根据ViewGroup.addView在ViewPager里面进行关键字搜索,并没有找到相应添加View的方法,所以这也是为什么在ViewPager.instantiateItem时候需要手动将Content View添加到ViewPager的原因,对应的,我们需要在ViewPager.destoryItem中将Content View从ViewPager移除。
Android中滚动具体有两种:一种是拖动Drag,一种是滑动Fling.ViewPager均实现了这两种滚动方式。
如果你对View的事件传递有一定的了解的话,那么你肯定会知道我们对View触摸事件的处理集中在以下三个方法中:
- public boolean onTouchEvent(MotionEvent ev)
- public boolean dispatchTouchEvent(MotionEvent ev)
- public boolean onInterceptTouchEvent(MotionEvent ev) ViewGroup独有
由于ViewPager只重写了onTouchEvent,所以我们直接从onTouchEvent去分析ViewPager.我们直接从MotionEvent的几个原子操作来一一分析ViewPager的滚动。
- public boolean onTouchEvent(MotionEvent ev)
先看看一些无关于触摸逻辑的部分,主要做了一些健壮性判断和参数初始化。
@Override public boolean onTouchEvent(MotionEvent ev) { if (mFakeDragging) {return true; } if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { return false; } if (mAdapter == null || mAdapter.getCount() == 0) {return false;} if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();} mVelocityTracker.addMovement(ev); final int action = ev.getAction(); boolean needsInvalidate = false; switch (action & MotionEventCompat.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { /**..省略Code..,下面会分析**/ break; } case MotionEvent.ACTION_MOVE: /**..省略Code..,下面会分析**/ break; case MotionEvent.ACTION_UP: /**..省略Code..,下面会分析**/ break; case MotionEvent.ACTION_CANCEL: /**..省略Code..,下面会分析**/ break; case MotionEventCompat.ACTION_POINTER_DOWN: { /**..省略Code..,下面会分析**/ break; } case MotionEventCompat.ACTION_POINTER_UP: /**..省略Code..,下面会分析**/ break; } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); } return true; }
- 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
- 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
接下来我们分析各个ACTION动作,这里强调一下ViewPager拖动的逻辑在ACTION_MOVE里面,滑动的逻辑在ACTION_UP里面。
MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_DOWN: { mScroller.abortAnimation(); mPopulatePending = false; populate(); mLastMotionX = mInitialMotionX = ev.getX(); mLastMotionY = mInitialMotionY = ev.getY(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); break; }
ACTION_DWON里面主要是记录起始的坐标值与有效的触摸手指,为后续的动作提供数据。ViewPager支持多点触摸,所以我们接着看多触摸相关的ACTION_PONTER_DOWN和ACTION_POINTER_UP。
MotionEvent.ACTION_POINTER_DOWN&MotionEvent.ACTION_POINTER_UP
case MotionEventCompat.ACTION_POINTER_DOWN: { final int index = MotionEventCompat.getActionIndex(ev); final float x = MotionEventCompat.getX(ev, index); mLastMotionX = x; mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); break;
MotionEvent.ACTION_POINTER_DOWN&MotionEvent.ACTION_POINTER_UP跟ACTION_DOWN的处理逻辑其实类似,均是记录更新当前的最新的X坐标与有效的触摸手指ID。
接下来重头戏来了,我们先看看ViewPager的拖动是怎么实现的
MotionEvent.ACTION_MOVE
case MotionEvent.ACTION_MOVE: if (!mIsBeingDragged) { final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); if (pointerIndex == -1) { needsInvalidate = resetTouch(); break; } final float x = MotionEventCompat.getX(ev, pointerIndex); final float xDiff = Math.abs(x - mLastMotionX); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mLastMotionY); if (xDiff > mTouchSlop && xDiff > yDiff) { mIsBeingDragged = true; requestParentDisallowInterceptTouchEvent(true); mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; mLastMotionY = y; setScrollState(SCROLL_STATE_DRAGGING); setScrollingCacheEnabled(true); ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } } if (mIsBeingDragged) { final int activePointerIndex = MotionEventCompat.findPointerIndex( ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); needsInvalidate |= performDrag(x); } 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
- 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
我们接着看拖动的实现方法,performDrag(float)方法。
private boolean performDrag(float x) { boolean needsInvalidate = false; final float deltaX = mLastMotionX - x; mLastMotionX = x; float oldScrollX = getScrollX(); float scrollX = oldScrollX + deltaX; /**..省略Code..,scrollX的具体计算逻辑**/ mLastMotionX += scrollX - (int) scrollX; scrollTo((int) scrollX, getScrollY()); /**pageScrolled函数主要做了两件事: 1、对Decor View 进行同步拖动 2、如果ViewPager自定义了页面动画,则会对自定义的过度动画进行处理 **/ pageScrolled((int) scrollX); return needsInvalidate; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
由此可见ViewPager最终通过scrollTo来实现拖动。我们接着看滑动实现。
MotionEvent.ACTION_UP
由于ACTION_UP较为复杂,所以我们先大致说下ACTION_UP做的事情:
1、确定最后滑动到的页面。我们在使用ViewPager时候,我们会发现如果拖动的页面距离不够,它会弹回原页面,逻辑就是在这里。
2、更新Content View
3、滑动到相应的页面,如果是滑动效果,则通过Scroller滑动,否则通过scrollTo滑动。
下面我们看具体代码。
case MotionEvent.ACTION_UP: if (mIsBeingDragged) { /** 包括currentPage-->当前页面 pageOffset-->页面偏移量, initalVelocity-->X轴滑动的速度 totalDelta-->与开始滑动时候相比较的滑动偏移量 **/ final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( velocityTracker, mActivePointerId); mPopulatePending = true; final int width = getClientWidth(); final int scrollX = getScrollX(); final ItemInfo ii = infoForCurrentScrollPosition(); final int currentPage = ii.position; final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor; final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); final int totalDelta = (int) (x - mInitialMotionX); int nextPage int =determineTargetPage(currentPage,pageOffset, setCurrentItemInternal(nextPage, true, true, initialVelocity); mActivePointerId = INVALID_POINTER; endDrag(); needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); } 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
- 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
我们接着看滑动实现的具体方法,setCurrentItemInternal(int,boolean ,boolean,int)方法。
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { /**..省略Code...,健壮性判断**/ if (item < 0) { item = 0; } else if (item >= mAdapter.getCount()) { item = mAdapter.getCount() - 1; } final int pageLimit = mOffscreenPageLimit; if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { for (int i=0; i<mItems.size(); i++) { mItems.get(i).scrolling = true; } } final boolean dispatchSelected = mCurItem != item; if (mFirstLayout) { mCurItem = item; /**..省略Code...,相应回调调用,自行阅读源码**/ requestLayout(); } else { populate(item); scrollToItem(item, smoothScroll, velocity, dispatchSelected); } }
- 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
- 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
最终执行滑动的方法是scrollToItem(int , boolean , int ,boolean ) 。话不多说,直接上代码。
private void scrollToItem(int item, boolean smoothScroll, int velocity, boolean dispatchSelected) { final ItemInfo curInfo = infoForPosition(item); int destX = 0; if (curInfo != null) { final int width = getClientWidth(); destX = (int) (width * Math.max(mFirstOffset, Math.min(curInfo.offset, mLastOffset))); } if (smoothScroll) { smoothScrollTo(destX, 0, velocity); if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item); } if (dispatchSelected && mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageSelected(item); } } else { if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item); } if (dispatchSelected && mInternalPageChangeListener != null) { mInternalPageChangeListener.onPageSelected(item); } completeScroll(false); scrollTo(destX, 0); pageScrolled(destX); } }
- 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
- 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
ViewPager的整个滚动实现过程就是这样了,这里再做简要的总结:
MotionEvent.ACTION_DOWN&MotionEvent.POINTER_DOWN&MotionEvent.POINTER_UP:主要更新了当前的触摸手指ID和最新的X坐标。
MotionEvent.ACTION_UP:处理滑动。
MotionEvent.ACTION_MOVE:处理拖动。
ViewPager需要PagerAdapter才能够实现相应的功能,而我们创建PagerAdapter的时候需要重写一些方法,才能使得ViewPager正常运作,所以这里我们这里主要看看这些方法与ViewPager的交互:
- public Object instantiateItem(ViewGroup,int)
- public void destroyItem(ViewGroup, int, Object)
- public abstract boolean isViewFromObject(View,Object)
- public abstract int getCount()
总共4个方法来分析PagerAdapter与ViewPager的交互。
instantiateItem(ViewGroup,int)
instantiateItem(ViewGroup,int)方法在ViewPager.addNewItem(int, int)中被调用,主要作用是创建一个页面的Content View。这里再强调一遍,在重写这个方法时候,需要主动将该Content View添加到ViewPager里面去,因为ViewPager没有主动添加该Content View为其Child View。
destroyItem(ViewGroup, int, Object)
相应的ViewPager不会主动的添加Content View,也不会主动的移除Content View,所以亦需要在重写该方法的时候主动从ViewPager中移除该Content View。在ViewPager以下方法会调用destroyItem(ViewGroup, int, Object):
- ViewPager.setAdapter().这里调用该方法去销毁旧Adapter的View
- ViewPager.dataSetChanged(),这里调用该方法去销毁过期数据的View
- ViewPager.populate().ViewPager有一个ViewPager会保持一定的页面数,所以这里要把不再持有的页面以及相应的Content View销毁。
isViewFromObject(View,Object)
在使用ViewPager的时候,我曾试过未重写该方法,导致ViewPager运行不正常,所以这里我们看看isViewFromObject(View,Object)对ViewPager会有什么影响。isViewFromObject(View,Object)在ViewPager.infoForChild(View)中被调用到。所以我们分析下ViewPager.infoForChild(View)方法。
ItemInfo infoForChild(View child) { for (int i=0; i<mItems.size(); i++) { ItemInfo ii = mItems.get(i); if (mAdapter.isViewFromObject(child, ii.object)) { return ii; } } return null; }
至此谜底揭开,我们需要重写isViewFromObject(View,Object) 让ViewPager.infoForChild(View )能够正确判断某个Child View对应的ItemInfo信息。但是这里还是存在疑问,为什么这里ViewPager不写死该方法呢?
getCount()
这个方法不用多说了,在ViewPager中用的比较广。自行阅读源码。
尾声
至此,整个ViewPager与PagerAdapter也已经初步解析完毕了,你学到东西了吗?