ViewPager
viewpager分析过程
initViewPager -> onAttachToWindow -> onMeasure -> onLayout -> onDraw
initViewpager()
void initViewPager() {
setWillNotDraw(false); //重写onDraw调用,为了在onDraw仿佛里绘制PageMarginDeawable
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setFocusable(true);
final Context context = getContext();
//初始化了一个scroller对象去处理滚动操作
mScroller = new Scroller(context, sInterpolator);
final ViewConfiguration configuration = ViewConfiguration.get(context);
final float density = context.getResources().getDisplayMetrics().density;
mTouchSlop = configuration.getScaledPagingTouchSlop();
mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
// 滑倒左右边界的效果
mLeftEdge = new EdgeEffect(context);
mRightEdge = new EdgeEffect(context);
mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
...
initViewPager 里做的事情
在初始化方法中,首先调用了 setWillNotDraw(false),说明viewPager 需要在onDraw里做些事情
然后是setDescendantFocusability(FOCUS_AFTER_DESCENDANTS)设置了viewPager 在什么情况下获取焦点
还配置了一些初始化的变量,涉及滑动的阈值,速度的范围,边界效果(EdgeEffect)
onAttachToWindow()
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// 标记是否是第一次执行layout
mFirstLayout = true;
}
onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
int size = getChildCount();
// measure DecorView
// 省略代码,
// childWidth 和childheigth 都需要减去decorView 所占空间
mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
// Make sure we have created all fragments that we need to have shown.
mInLayout = true;
populate();
mInLayout = false;
// Page views next.
size = getChildCount();
for (int i = 0; i < size; ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
if (DEBUG) {
Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
}
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);
}
}
}
}
onlayout
@Override
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();
// 省略layout decorView 代码
final int childWidth = width - paddingLeft - paddingRight;
// Page views. Do this once we have the right padding offsets from above.
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;
// lp.needMeasure 在viewPager 的addView 方法里赋的值
if (lp.needsMeasure) {
// This was added during layout and needs measurement.
// Do it now that we know what we're working with.
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);
}
if (DEBUG) {
Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
+ ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
+ "x" + child.getMeasuredHeight());
}
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
// 如果是第一次layout,则scroll到对应的位置
if (mFirstLayout) {
scrollToItem(mCurItem, false, 0, false);
}
mFirstLayout = false;
}
setAdapter()
public void setAdapter(@Nullable PagerAdapter adapter) {
// 设置adapter, 如果是重新设置新的adapter,那么需要清理状态
if (mAdapter != null) {
mAdapter.setViewPagerObserver(null);
// 可以在pagerAdapter里重写这个方法,在viewPager正式开始展示之前做一些操作
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
//结束更新,和startUpdate 是一对,FragmentPageAdapter里重写该方法提交事物
mAdapter.finishUpdate(this);
mItems.clear();
//移除所有的decorView
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
// 这里的observer 就是调用nitifyDataSetChanged用于更新操作
mAdapter.setViewPagerObserver(mObserver);
//这个变量是用于在执行核心方法populate()的时候避免与滑动事件产生冲突
mPopulatePending = false;
// 这里把全局变量的值转为局部变量
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
// 期望的要展示的view的数量
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();
}
}
// 如果设置了adapterChange的监听,回调的同时把oldAdapter 和newAdapter 返回
if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
}
}
}
populate()
populate 方法的执行是很频繁的,在setAdapter里除了算是基础执行之外,每次滑动停止,或者调用serCurrentItem 方法,都会执行,甚至会执行两次
void populate() {
populate(mCurItem);
}
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
if (mCurItem != newCurrentItem) {
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
if (mAdapter == null) {
sortChildDrawingOrder();
return;
}
// Bail now if we are waiting to populate. This is to hold off
// on creating views from the time the user releases their finger to
// fling to a new position until we have finished the scroll to
// that position, avoiding glitches from happening at that point.
//因为这个方法是在多种情况下调用的,所以为了避免和滑动引起的冲突,需要延迟这部分绘制
if (mPopulatePending) {
sortChildDrawingOrder();
return;
}
// Also, don't populate until we are attached to a window. This is to
// avoid trying to populate before we have restored our view hierarchy
// state and conflicting with what is restored.
if (getWindowToken() == null) {
return;
}
mAdapter.startUpdate(this);
// 获取预加载的item的数量,通过setOffScreenPageLimit设置
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);
//如果发现N不一样了,表明adapter的数据源变化了,但没有notifyDataSetChanged
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());
}
// 优先找到currentItem
// Locate the currently focused item or add it if needed.
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);
}
addNewItem()
static class ItemInfo {
Object object;
int position;
boolean scrolling;
float widthFactor; //宽度占比
float offset; // 偏移量
}
ItemInfo addNewItem(int position, int index) {
ItemInfo ii = new ItemInfo();
ii.position = position;
ii.object = mAdapter.instantiateItem(this, position);
ii.widthFactor = mAdapter.getPageWidth(position);
if (index < 0 || index >= mItems.size()) {
mItems.add(ii);
} else {
mItems.add(index, ii);
}
return ii;
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
final LayoutParams lp = (LayoutParams) params;
lp.isDecor |= isDecorView(child);
if (mInLayout) {
if (lp != null && lp.isDecor) {
throw new IllegalStateException("Cannot add pager decor view during layout");
}
lp.needsMeasure = true;
addViewInLayout(child, index, params);
} else {
super.addView(child, index, params);
}
}
if (curItem == null && N > 0) {
//如果当前item为null, 并且期望展示的view数量是大于0 ,通过addNewItem 添加
curItem = addNewItem(mCurItem, curIndex);
}
// Fill 3x the available width or up to the number of offscreen
// pages requested to either side, whichever is larger.
// If we have no current item we have no work to do.
if (curItem != null) {
float extraWidthLeft = 0.f;
// 这里是前一个页面的iteminfo 在itemInfos里的position ,并不是viewPager 里实际位置
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;
// currentItem 左边填充和销毁
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);
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;
}
}
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++) {
if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
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));
}
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
} else if (ii != null && pos == ii.position) {
extraWidthRight += ii.widthFactor;
itemIndex++;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
} else {
ii = addNewItem(pos, itemIndex);
itemIndex++;
extraWidthRight += ii.widthFactor;
ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
}
}
}
// 填充完毕,计算偏移量,为后面layout作准备
calculatePageOffsets(curItem, curIndex, oldCurInfo);
}
// 告诉适配器当前选中的item信息(其实可以在这里面做很多事情,比如对于banner来说,在这个方法回调是对view进行一些操作,如播放视频,直播等等)
mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
// 通知更新结束
mAdapter.finishUpdate(this);
// Check width measurement of current pages and drawing sort order.
// Update LayoutParams as needed.
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) {
// 0 means requery the adapter for this, it doesn't have a valid width.
final ItemInfo ii = infoForChild(child);
if (ii != null) {
lp.widthFactor = ii.widthFactor;
lp.position = ii.position;
}
}
}
// 对child按照position 从小到大排序 (并不一定会排序,内部做了判断)
sortChildDrawingOrder();
// 子View 获取焦点
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;
}
}
}
}
}
}
calculatePageOffsets()
计算每一页的偏移量
private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
final int N = mAdapter.getCount();
final int width = getClientWidth();
final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
// 第一部分,根据oldCurInfo 去计算新的curInfo 的偏移量
if (oldCurInfo != null) {
final int oldCurPosition = oldCurInfo.position;
// Base offsets off of oldCurInfo.
if (oldCurPosition < curItem.position) {
int itemIndex = 0;
ItemInfo ii = null;
float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
for (int pos = oldCurPosition + 1;
pos <= curItem.position && itemIndex < mItems.size(); pos++) {
ii = mItems.get(itemIndex);
while (pos > ii.position && itemIndex < mItems.size() - 1) {
itemIndex++;
ii = mItems.get(itemIndex);
}
while (pos < ii.position) {
// We don't have an item populated for this,
// ask the adapter for an offset.
offset += mAdapter.getPageWidth(pos) + marginOffset;
pos++;
}
ii.offset = offset;
offset += ii.widthFactor + marginOffset;
}
} else if (oldCurPosition > curItem.position) {
int itemIndex = mItems.size() - 1;
ItemInfo ii = null;
float offset = oldCurInfo.offset;
for (int pos = oldCurPosition - 1;
pos >= curItem.position && itemIndex >= 0; pos--) {
ii = mItems.get(itemIndex);
while (pos < ii.position && itemIndex > 0) {
itemIndex--;
ii = mItems.get(itemIndex);
}
while (pos > ii.position) {
// We don't have an item populated for this,
// ask the adapter for an offset.
offset -= mAdapter.getPageWidth(pos) + marginOffset;
pos--;
}
offset -= ii.widthFactor + marginOffset;
ii.offset = offset;
}
}
}
// Base all offsets off of curItem.
final int itemCount = mItems.size();
float offset = curItem.offset;
int pos = curItem.position - 1;
// 这里的firstOffset 和oldOffset 用于标记是否到边界
mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
mLastOffset = curItem.position == N - 1
? curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
// 第二部分,根据curInfo 的偏移量,计算itemList 里curInfo 之前的info的偏移量
for (int i = curIndex - 1; i >= 0; i--, pos--) {
final ItemInfo ii = mItems.get(i);
while (pos > ii.position) {
offset -= mAdapter.getPageWidth(pos--) + marginOffset;
}
offset -= ii.widthFactor + marginOffset;
ii.offset = offset;
if (ii.position == 0) mFirstOffset = offset;
}
offset = curItem.offset + curItem.widthFactor + marginOffset;
pos = curItem.position + 1;
// 第三部分,计算curInfo 后面的item的偏移量
for (int i = curIndex + 1; i < itemCount; i++, pos++) {
final ItemInfo ii = mItems.get(i);
while (pos < ii.position) {
offset += mAdapter.getPageWidth(pos++) + marginOffset;
}
if (ii.position == N - 1) {
mLastOffset = offset + ii.widthFactor - 1;
}
ii.offset = offset;
offset += ii.widthFactor + marginOffset;
}
mNeedCalculatePageOffsets = false;
}
setCurrentItem()
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
if (mAdapter == null || mAdapter.getCount() <= 0) {
setScrollingCacheEnabled(false);
return;
}
if (!always && mCurItem == item && mItems.size() != 0) {
setScrollingCacheEnabled(false);
return;
}
if (item < 0) {
item = 0;
} else if (item >= mAdapter.getCount()) {
item = mAdapter.getCount() - 1;
}
final int pageLimit = mOffscreenPageLimit;
// 如果我们需要跨页跳转,则不希望在这个过程中对item进行回收
if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
// We are doing a jump by more than one page. To avoid
// glitches, we want to keep all current pages in the view
// until the scroll ends.
for (int i = 0; i < mItems.size(); i++) {
mItems.get(i).scrolling = true;
}
}
final boolean dispatchSelected = mCurItem != item;
if (mFirstLayout) {
// We don't have any idea how big we are yet and shouldn't have any pages either.
// Just set things up and let the pending layout handle things.
mCurItem = item;
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
requestLayout();
} else {
populate(item);
//onPageSelect 的回调在scrollToItem方法里
scrollToItem(item, smoothScroll, velocity, dispatchSelected);
}
}
我们知道onPageSelect回调是发生在页面完全被选中之前的,也就是说只要确定了接下来的页面,就会回调onPageSelect。
那么如果我们想在onPageSelect中去获得正在展示的view的时候,在某些情况下获取的会是null, 那是因为还没有执行populate方法。
举个例子我们在fragment的onViewCreated里初始化viewPager ,当数据请求回来时,才setAdapter,这时候我们知道 mFirstLayout 这个变量已经是false了,所以在setAdapter时,是会优先执行populate方法的,假设offsetPagelimit 为1,那么分情况:
setCurrentItem(1) : 此时由于populate进行过填充第0项和第1项,所以在onPageSelect里去获取这个view是可以获取到的
setCurrentItem(2) ,此时如果直接获取的话是null,那么通常这里我们会通过postDelayed 来获取,因为setCurrentItem 之后,紧接着会执行onMeasure方法,在onMeausre方法里会再一次执行populate进行填充,所以在onPageSelect里就能获取到我们想要的view
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) {
dispatchOnPageSelected(item);
}
} else {
if (dispatchSelected) {
dispatchOnPageSelected(item);
}
//否则,直接调用scrollTo 到达位置,触发onPageScrolled
completeScroll(false);
scrollTo(destX, 0);
pageScrolled(destX);
}
}
smoothScrollTo , 顾名思义,就是平滑的滚动到某个位置,其实里面的代码主要是计算了滑动到currItem所需的时间, 其中滑动的时间Math.min(duration, MAX_SETTLE_DURATION),表示了所需的时间不超过600ms
void smoothScrollTo(int x, int y, int velocity) {
if (getChildCount() == 0) {
// Nothing to do.
setScrollingCacheEnabled(false);
return;
}
int sx;
boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
if (wasScrolling) {
// We're in the middle of a previously initiated scrolling. Check to see
// whether that scrolling has actually started (if we always call getStartX
// we can get a stale value from the scroller if it hadn't yet had its first
// computeScrollOffset call) to decide what is the current scrolling position.
sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
// And abort the current scrolling.
mScroller.abortAnimation();
setScrollingCacheEnabled(false);
} else {
sx = getScrollX();
}
int sy = getScrollY();
int dx = x - sx;
int dy = y - sy;
if (dx == 0 && dy == 0) {
completeScroll(false);
populate();
setScrollState(SCROLL_STATE_IDLE);
return;
}
setScrollingCacheEnabled(true);
setScrollState(SCROLL_STATE_SETTLING);
final int width = getClientWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth
* distanceInfluenceForSnapDuration(distanceRatio);
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
// Reset the "scroll started" flag. It will be flipped to true in all places
// where we call computeScrollOffset().
mIsScrollStarted = false;
mScroller.startScroll(sx, sy, dx, dy, duration);
ViewCompat.postInvalidateOnAnimation(this);
}
可以看到,三个回调我们到现在为止只见到了pageSelect ,接下来看pageScrolled 和pageStateChanged 的调用
public void computeScroll() {
mIsScrollStarted = true;
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (!pageScrolled(x)) {
mScroller.abortAnimation();
scrollTo(0, y);
}
}
// Keep on drawing until the animation has finished.
ViewCompat.postInvalidateOnAnimation(this);
return;
}
// Done with scroll, clean up state.
completeScroll(true);
}
pageScrolled 里调用了onPageScrolled
onPageScrolled()
@CallSuper
protected void onPageScrolled(int position, float offset, int offsetPixels) {
// Offset any decor views if needed - keep them on-screen at all times.
if (mDecorChildCount > 0) {
final int scrollX = getScrollX();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
final int width = getWidth();
final int childCount = getChildCount();
// 处理decorView的滑动,通过改变left的值完成滑动
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) continue;
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int childLeft = 0;
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
paddingLeft += child.getWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
paddingLeft);
break;
case Gravity.RIGHT:
childLeft = width - paddingRight - child.getMeasuredWidth();
paddingRight += child.getMeasuredWidth();
break;
}
childLeft += scrollX;
final int childOffset = childLeft - child.getLeft();
if (childOffset != 0) {
child.offsetLeftAndRight(childOffset);
}
}
}
// 看到这里,这里就是回调OnPageChangeListener的onPageScrolled方法
dispatchOnPageScrolled(position, offset, offsetPixels);
// pageTransformer计算处理
if (mPageTransformer != null) {
final int scrollX = getScrollX();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.isDecor) continue;
final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
}
mCalledSuper = true;
}
private void completeScroll(boolean postEvents) {
boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
if (needPopulate) {
// Done with scroll, no longer want to cache view drawing.
setScrollingCacheEnabled(false);
boolean wasScrolling = !mScroller.isFinished();
if (wasScrolling) {
mScroller.abortAnimation();
int oldX = getScrollX();
int oldY = getScrollY();
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
scrollTo(x, y);
if (x != oldX) {
pageScrolled(x);
}
}
}
}
mPopulatePending = false;
for (int i = 0; i < mItems.size(); i++) {
ItemInfo ii = mItems.get(i);
if (ii.scrolling) {
needPopulate = true;
ii.scrolling = false;
}
}
if (needPopulate) {
if (postEvents) {
ViewCompat.postOnAnimation(this, mEndScrollRunnable);
} else {
mEndScrollRunnable.run();
}
}
}
private final Runnable mEndScrollRunnable = new Runnable() {
@Override
public void run() {
setScrollState(SCROLL_STATE_IDLE);
populate();
}
};
结束滑动后,设置state,同时在执行一次populate,由于setCurrentItem方法里我们可能设置了itemInfo.isScrolling,导致在执行populate时没有回收这些item,这里的populate就刚好将该回收的item回收掉
ACTION_MOVE
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0
? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollingCacheEnabled(true);
}
performDrag(x);
private boolean performDrag(float x) {
// ... 省略代码,主要就是计算scrollX,水平滚动距离
// 然后分别调用scrollTo方法和pageScrolled方法
scrollTo((int) scrollX, getScrollY());
pageScrolled((int) scrollX);
return needsInvalidate;
}
ACTION_UP
// 特殊说明下,这里infoForCurrentScrollPosition 方法得到的实际上是当前屏幕内可见的最左边的item的position
final ItemInfo ii = infoForCurrentScrollPosition();
int currentPage = ii.position;
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity);
notifyDataSetChanged
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
mAdapter.setViewPagerObserver(mObserver);
void dataSetChanged() {
final int adapterCount = mAdapter.getCount();
mExpectedAdapterCount = adapterCount;
boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
&& mItems.size() < adapterCount;
int newCurrItem = mCurItem;
boolean isUpdating = false;
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
final int newPos = mAdapter.getItemPosition(ii.object);
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
if (!isUpdating) {
mAdapter.startUpdate(this);
isUpdating = true;
}
mAdapter.destroyItem(this, ii.position, ii.object);
needPopulate = true;
if (mCurItem == ii.position) {
// Keep the current item in the valid range
newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
needPopulate = true;
}
continue;
}
if (ii.position != newPos) {
if (ii.position == mCurItem) {
// Our current item changed position. Follow it.
newCurrItem = newPos;
}
ii.position = newPos;
needPopulate = true;
}
}
if (isUpdating) {
mAdapter.finishUpdate(this);
}
Collections.sort(mItems, COMPARATOR);
if (needPopulate) {
// Reset our known page widths; populate will recompute them.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.isDecor) {
lp.widthFactor = 0.f;
}
}
setCurrentItemInternal(newCurrItem, false, true);
requestLayout();
}
}
/**
* Called when the host view is attempting to determine if an item's position
* has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
* item has not changed or {@link #POSITION_NONE} if the item is no longer present
* in the adapter.
*
* <p>The default implementation assumes that items will never
* change position and always returns {@link #POSITION_UNCHANGED}.
*
* @param object Object representing an item, previously returned by a call to
* {@link #instantiateItem(View, int)}.
* @return object's new position index from [0, {@link #getCount()}),
* {@link #POSITION_UNCHANGED} if the object's position has not changed,
* or {@link #POSITION_NONE} if the item is no longer present.
*/
public int getItemPosition(Object object) {
return POSITION_UNCHANGED;
}
翻译一下,大概意思就是: 如果一个item的位置没有发生变化,则直接返回POSITION_UNCHANGED,如果一个item的位置发生了变化,则返回他的新position,如果他被移除了,则需要返回POSITION_NONE, 这样该item就会被移除。
PageAdapter
FragmentPageAdapter()
@SuppressWarnings("ReferenceEquality")
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//根据item的位置给item生成一个id,这里默认的getItemId 获取的就是position
final long itemId = getItemId(position);
//根据viewpager的id和item的id,给viewpager对应item位置的fragment生成一个name
String name = makeFragmentName(container.getId(), itemId);
//根据这个name找到fragment实例
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
Android 8.1
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
AndroidX
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
fragment.setUserVisibleHint(true);
}
mCurrentPrimaryItem = fragment;
}
}
setPrimaryItem 方法主要内容是调用被选中的fragment的setUserVisibleHint方法设置为true,被切换走的fragment 调用setUserVisibleHint(false),但是我们也看到了
mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
@Override
public void finishUpdate(@NonNull ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
}
}
FragmentPageStateAdapter
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
PageTransformer
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformPage(@NonNull View page, float position);
}
protected void onPageScrolled(int position, float offset, int offsetPixels) {
....
...
if (mPageTransformer != null) {
final int scrollX = getScrollX();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.isDecor) continue;
final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
}
mCalledSuper = true;
}
public class DepthPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
if (position < -1) { // [-Infinity,-1)
view.setAlpha(0);
} else if (position <= 0) { // [-1,0]
view.setAlpha(1);
view.setTranslationX(0);
view.setScaleX(1);
view.setScaleY(1);
} else if (position <= 1) { // (0,1]
view.setTranslationX(pageWidth * -position);
Log.d("zhangxiangyu",pageWidth+" ");
float scaleFactor = MIN_SCALE
+ (1 - MIN_SCALE) * (1 - Math.abs(position));
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
} else { // (1,+Infinity]
view.setAlpha(0);
}
}
}
setMaxLifecycle
其实在最新的fragment代码里,发现setUserVisibleHint已经被废弃了,转而使用setMaxLifecycle 来代替
fragment 一直是无法设置生命周期的,其onResume ,onPause等周期方法只会根据所依附activity的生命周期变化而变化.
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
@NonNull Lifecycle.State state) {
addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
return this;
}
state Lifecycle.State枚举类型,该参数的使用条件是至少是Lifecycle.State.CREATED,否则报IllegalArgumentException异常
Fragment 定义了一下五种状态,
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // Fully created, not started.
static final int STARTED = 3; // Created and started, not resumed.
static final int RESUMED = 4; // Created started and resumed.
这里的state和lifeCycle 中的state虽然不一样,但是逻辑上处理是一样的。那么我们把framgent的生命周期按照顺序
onCreate->onCretateView->onStart->onResume->onPause->onStop-> onDestoryView->onDestory
同样的我们把fragment的生命周期状态也按照顺序列出来
CREATED->STARTED->RESUMED
那我们知道,在fragmentManagerImpl 的moveToState 方法里,根据fragment当前的state和newState,会分别调用不同的生命周期方法,我这里搬出来我们看下
if(f.mState <=newState)
{
...
switch (f.mState) {
case Fragment.INITIALIZING:
performAttach();
performCreate();
// fall through
case Fragment.CREATED:
// We want to unconditionally run this anytime we do a moveToState that
// moves the Fragment above INITIALIZING, including cases such as when
// we move from CREATED => CREATED as part of the case fall through above.
if (newState > Fragment.CREATED) {
performCreateView()
performActivityCreated();
}
// fall through
case Fragment.ACTIVITY_CREATED:
if (newState > Fragment.ACTIVITY_CREATED) {
performStart();
}
// fall through
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
f.performResume();
}
}
} else if(f.mState >newState)
{
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
performPause();
}
// fall through
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
performStop();
}
// fall through
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
performDestroyView();
f.mContainer = null;
f.mView = null;
// Set here to ensure that Observers are called after
// the Fragment's view is set to null
f.mViewLifecycleOwner = null;
f.mViewLifecycleOwnerLiveData.setValue(null);
f.mInnerView = null;
f.mInLayout = false;
}
// fall through
case Fragment.CREATED:
boolean beingRemoved = f.mRemoving && !f.isInBackStack();
if (beingRemoved || mNonConfig.shouldDestroy(f)) {
performDestroy();
} else {
mState = Fragment.INITIALIZING;
}
performDetach();
}
}
// 避免滑动冲突
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
final int scrollY = v.getScrollY();
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
// TODO: Add versioned support here for transformed views.
// This will not work for transformed views in Honeycomb+
final View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
&& y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
&& canScroll(child, true, dx, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
}
}
return checkV && v.canScrollHorizontally(-dx);
}
ViewPager2
ViewPager有两个弊端:不能关闭预加载和notifyDataSetChanged的使用,所以开头我为什么说offscreenPageLimit在ViewPager上十分不友好;本质上是因为offscreenPageLimit不能设置成0
LinearLayoutManager mLayoutManager;
private int mPendingCurrentItem = NO_POSITION;
private Parcelable mPendingAdapterState;
private RecyclerView mRecyclerView;
private PagerSnapHelper mPagerSnapHelper; // 用来保证每次只滑一页
// 用来分发事件的,将recyclerview的Scroll事件转换为pageSelect,pageScrolled,pageOnScrollStateChanged
private ScrollEventAdapter mScrollEventAdapter;
private PageTransformerAdapter mPageTransformerAdapter;
private boolean mUserInputEnabled = true;
private @OffscreenPageLimit int mOffscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT;
先看如何实现viewPager的效果,看下PageSnapHelper
首先我们需要知道SnapHelper是如何工作的
SnapHelper 里有一个方法叫做snapToTargetExistingView ,内部通过调用findSnapView找到需要对齐的view ,再调用calculateDistanceToFinalSnap 计算如果想要对齐需要滚动的距离,最后调用smoothScrollBy滚动到对应的位置。
那么这就处理了对齐的问题,那么snapToTargetExistingView 方法是什么时候调用的呢,一个是在attachToRecyclerView 时需要对齐,另外调用就是zai滑动停止时调用。
之后就是解决惯性滑动的问题,SnapHelper 中通过snapFromFling 方法接管recyclerView的fling滑动,在snapFromFling 方法里通过调用findTargetSnapPosition方法,根据滑动的速率来计算出需要滚动到的postion ,最后再通过smoothScroller 执行滑动。 所以我们只需要重写三个方法 calculateDistanceToFinalSnap, findSnapView,findTargetSnapPosition 即可
// 计算targetView 到viewPager中心点的距离
public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(layoutManager, targetView,
getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
return out;
}
private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
@NonNull View targetView, OrientationHelper helper) {
final int childCenter = helper.getDecoratedStart(targetView)
+ (helper.getDecoratedMeasurement(targetView) / 2);
final int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
// 找到当前距离中心点最近的view
@Nullable
@Override
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager.canScrollVertically()) {
return findCenterView(layoutManager, getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
}
return null;
}
@Nullable
private View findCenterView(RecyclerView.LayoutManager layoutManager,
OrientationHelper helper) {
int childCount = layoutManager.getChildCount();
if (childCount == 0) {
return null;
}
View closestChild = null;
final int center;
if (layoutManager.getClipToPadding()) {
center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
center = helper.getEnd() / 2;
}
int absClosest = Integer.MAX_VALUE;
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
int childCenter = helper.getDecoratedStart(child)
+ (helper.getDecoratedMeasurement(child) / 2);
int absDistance = Math.abs(childCenter - center);
/* if child center is closer than previous closest, set it as closest */
if (absDistance < absClosest) {
absClosest = absDistance;
closestChild = child;
}
}
return closestChild;
}
// 找到滑动后目标position
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
final int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
final OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
if (orientationHelper == null) {
return RecyclerView.NO_POSITION;
}
// A child that is exactly in the center is eligible for both before and after
View closestChildBeforeCenter = null;
int distanceBefore = Integer.MIN_VALUE;
View closestChildAfterCenter = null;
int distanceAfter = Integer.MAX_VALUE;
// Find the first view before the center, and the first view after the center
final int childCount = layoutManager.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
if (child == null) {
continue;
}
final int distance = distanceToCenter(layoutManager, child, orientationHelper);
if (distance <= 0 && distance > distanceBefore) {
// Child is before the center and closer then the previous best
distanceBefore = distance;
closestChildBeforeCenter = child;
}
if (distance >= 0 && distance < distanceAfter) {
// Child is after the center and closer then the previous best
distanceAfter = distance;
closestChildAfterCenter = child;
}
}
// Return the position of the first child from the center, in the direction of the fling
final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);
if (forwardDirection && closestChildAfterCenter != null) {
return layoutManager.getPosition(closestChildAfterCenter);
} else if (!forwardDirection && closestChildBeforeCenter != null) {
return layoutManager.getPosition(closestChildBeforeCenter);
}
// There is no child in the direction of the fling. Either it doesn't exist (start/end of
// the list), or it is not yet attached (very rare case when children are larger then the
// viewport). Extrapolate from the child that is visible to get the position of the view to
// snap to.
View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter;
if (visibleView == null) {
return RecyclerView.NO_POSITION;
}
int visiblePosition = layoutManager.getPosition(visibleView);
int snapToPosition = visiblePosition
+ (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1);
if (snapToPosition < 0 || snapToPosition >= itemCount) {
return RecyclerView.NO_POSITION;
}
return snapToPosition;
}
OffscreenPageLimit 在viewPager2里是如何应用的
@Override
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
@NonNull int[] extraLayoutSpace) {
int pageLimit = getOffscreenPageLimit();
if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
// Only do custom prefetching of offscreen pages if requested
super.calculateExtraLayoutSpace(state, extraLayoutSpace);
return;
}
final int offscreenSpace = getPageSize() * pageLimit;
extraLayoutSpace[0] = offscreenSpace;
extraLayoutSpace[1] = offscreenSpace;
}
找一下OffscreenPageLimit 只有在这里用到,实际上就是扩充了recyclerView
的填充范围,前后各扩充 getPageSize() * pageLimit 距离。
那么如何理解offscreenPageLimit 对framgent的影响呢,viewPager里,如果我们设置了offscreenPageLimit 为1,假如此时是0,1,当我们滑动到2的时候,会对0进行回收。但是对于viewPager2来说,由于recyclerView 本身是有缓存的,一般是2项,那么由于offScreenPageLimit 为1,所以最多能承载5个fragment。
FragmentStateAdapter
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
static FragmentViewHolder create(ViewGroup parent) {
FrameLayout container = new FrameLayout(parent.getContext());
container.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
container.setId(ViewCompat.generateViewId());
container.setSaveEnabled(false);
return new FragmentViewHolder(container);
}
@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
// 获取holder 的itemId ,这里为position
final long itemId = holder.getItemId();
// 获取holder 里的framelayout 的id
final int viewHolderId = holder.getContainer().getId();
// 如果当前ItemView已经加载了Fragment,并且不是同一个Fragment,移除
final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
if (boundItemId != null && boundItemId != itemId) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
//保证对应位置的Fragment已经初始化,并且放在mFragments中
ensureFragment(position);
··· 省略代码
}
大部分fragment都是在onViewAttachedToWindow 在进行绑定操作的
@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
placeFragmentInViewHolder(holder);
gcFragments();
}
placeFragmentInViewHolder
Fragment是否添加到ItemView 中。
Fragment的View是否已经创建。
Fragment的View 是否添加视图树中
void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {
FrameLayout container = holder.getContainer();
View view = fragment.getView();
// ······
// 1.Fragment未添加到ItemView中,但是View已经创建
// 非法状态
if (!fragment.isAdded() && view != null) {
throw new IllegalStateException("Design assumption violated.");
}
// 2.Fragment添加到ItemView中,但是View未创建
// 先等待View创建完成,然后将View添加到Container。
if (fragment.isAdded() && view == null) {
scheduleViewAttach(fragment, container);
return;
}
// 3.Fragment添加到ItemView中,同时View已经创建完成并且添加到Container中
// 需要保证View添加到正确的Container中。
if (fragment.isAdded() && view.getParent() != null) {
if (view.getParent() != container) {
addViewToContainer(view, container);
}
return;
}
// 4.Fragment添加到ItemView中,同时View已经创建完成但是未添加到Container中
// 需要将View添加到Container中。
if (fragment.isAdded()) {
addViewToContainer(view, container);
return;
}
// 5.Fragment未创建,View未创建、未添加
if (!shouldDelayFragmentTransactions()) {
scheduleViewAttach(fragment, container);
mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).commitNow();
} else {
// 调用了第5步,但是Fragment还未真正创建
if (mFragmentManager.isDestroyed()) {
return; // nothing we can do
}
mLifecycle.addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (shouldDelayFragmentTransactions()) {
return;
}
source.getLifecycle().removeObserver(this);
if (ViewCompat.isAttachedToWindow(holder.getContainer())) {
placeFragmentInViewHolder(holder);
}
}
});
}
}
fragment是在onViewRecycled 回收的,当有viewHolder 被丢入RecyclerPool时候这个方法才会回调
@Override
public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
final int viewHolderId = holder.getContainer().getId();
final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
if (boundItemId != null) {
removeFragment(boundItemId);
mItemIdToViewHolder.remove(boundItemId);
}
}
@CallSuper
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
checkArgument(mFragmentMaxLifecycleEnforcer == null);
mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
mFragmentMaxLifecycleEnforcer.register(recyclerView);
}
/**
* Pauses (STARTED) all Fragments that are attached and not a primary item.
* Keeps primary item Fragment RESUMED.
*/
class FragmentMaxLifecycleEnforcer {
private ViewPager2.OnPageChangeCallback mPageChangeCallback;
private RecyclerView.AdapterDataObserver mDataObserver;
private LifecycleEventObserver mLifecycleObserver;
private ViewPager2 mViewPager;
void updateFragmentMaxLifecycle(boolean dataSetChanged) {
//...省略代码
for (int ix = 0; ix < mFragments.size(); ix++) {
long itemId = mFragments.keyAt(ix);
Fragment fragment = mFragments.valueAt(ix);
if (!fragment.isAdded()) {
continue;
}
transaction.setMaxLifecycle(fragment, itemId == mPrimaryItemId ? RESUMED : STARTED);
fragment.setMenuVisibility(itemId == mPrimaryItemId);
}
if (!transaction.isEmpty()) {
transaction.commitNow();
}
}
onPageSelect, onPageScrolled, onPageScrollStateChanged在ViewPager2中的调用
final class ScrollEventAdapter extends RecyclerView.OnScrollListener
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if ((mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG
|| mScrollState != SCROLL_STATE_DRAGGING)
&& newState == RecyclerView.SCROLL_STATE_DRAGGING) {
startDrag(false);
return;
}
// dragging -> setting
if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_SETTLING) {
if (mScrollHappened) {
dispatchStateChanged(SCROLL_STATE_SETTLING);
}
return;
}
// dragging -> idle
if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_IDLE) {
boolean dispatchIdle = false;
updateScrollEventValues();
// 没有发生滑动,那两种情况,一种是在边界,另外就是no page
if (!mScrollHappened) {
if (mScrollValues.mPosition != RecyclerView.NO_POSITION) {
dispatchScrolled(mScrollValues.mPosition, 0f, 0);
}
dispatchIdle = true;
} else if (mScrollValues.mOffsetPx == 0) {
dispatchIdle = true;
if (mDragStartPosition != mScrollValues.mPosition) {
dispatchSelected(mScrollValues.mPosition);
}
}
if (dispatchIdle) {
dispatchStateChanged(SCROLL_STATE_IDLE);
resetState();
}
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
mScrollHappened = true;
updateScrollEventValues();
if (mDispatchSelected) {
// Drag started settling, need to calculate target page and dispatch onPageSelected now
mDispatchSelected = false;
boolean scrollingForward = dy > 0 || (dy == 0 && dx < 0 == mViewPager.isRtl());
// "&& values.mOffsetPx != 0": filters special case where we're scrolling forward and
// the first scroll event after settling already got us at the target
mTarget = scrollingForward && mScrollValues.mOffsetPx != 0
? mScrollValues.mPosition + 1 : mScrollValues.mPosition;
if (mDragStartPosition != mTarget) {
dispatchSelected(mTarget);
}
} else if (mAdapterState == STATE_IDLE) {
// onScrolled while IDLE means RV has just been populated after an adapter has been set.
// Contract requires us to fire onPageSelected as well.
int position = mScrollValues.mPosition;
// Contract forbids us to send position = -1 though
dispatchSelected(position == NO_POSITION ? 0 : position);
}
// If position = -1, there are no items. Contract says to send position = 0 instead.
dispatchScrolled(mScrollValues.mPosition == NO_POSITION ? 0 : mScrollValues.mPosition,
mScrollValues.mOffset, mScrollValues.mOffsetPx);
// Dispatch idle in onScrolled instead of in onScrollStateChanged because RecyclerView
// doesn't send IDLE event when using setCurrentItem(x, false)
if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)
&& mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {
// When the target page is reached and the user is not dragging anymore, we're settled,
// so go to idle.
// Special case and a bit of a hack when mTarget == NO_POSITION: RecyclerView is being
// initialized and fires a single scroll event. This flags mScrollHappened, so we need
// to reset our state. However, we don't want to dispatch idle. But that won't happen;
// because we were already idle.
dispatchStateChanged(SCROLL_STATE_IDLE);
resetState();
}
}
private static final class ScrollEventValues {
int mPosition; // 这个position 其实就是当前可见的最左边的position
float mOffset;
int mOffsetPx;
网友评论