企图看懂源码细节是件伤神的事。
开源项目 CircleIndicator,从这张 gif 看来,好像没能理想地工作。
Measure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),getDefaultSize(0, heightMeasureSpec));
final int measuredWidth = getMeasuredWidth();
// Children are just made to fill our space.
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
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;
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);
}
}
}
}
子视图的大小,默认是 ViewPager 减去自身 padding 之后的空间。
宽度上还会乘以 widthFactor。
public float getPageWidth(int position) { return 1.f;}
默认是 1,所以一般到看到的都是充满整个 ViewPager 的子视图。
Layout
@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 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;
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
if (mFirstLayout) {
scrollToItem(mCurItem, false, 0, false);
}
mFirstLayout = false;
}
子视图的大小在 Measure 已经确定了,Layout 负责确定 childTop 和 childLeft,而
childTop = paddingTop;
childLeft = paddingLeft + (width - paddingLeft - paddingRight)*ii.offset;
关键是就是 offset 了,它是一个百分比的偏移量。
Populate
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;
}
}
}
Measure 时调用了 Populate 函数。
Populate 函数很长,这里是最关键的部分,决定了右侧子视图(左侧类似)中,哪些子视图该被创建,哪些子视图被该被销毁。(节约内存)
创建和销毁使用的是 PageAdapter 的 instantiateItem 和 destroyItem 方法。
判断的标准有两个:
if (extraWidthRight >= rightWidthNeeded && pos > endPos)
一方面是从缓存子视图的宽度总和上限制,不能超过 2,即 ViewPager 宽度的两倍。
final int endPos = Math.min(N-1, mCurItem + pageLimit);
另一方面是从缓存子视图的数量上来限制。
两个条件都超出时,才会销毁子视图,否则就确保子视图被创建。
calculatePageOffsets
Populate 函数的末尾调用了 calculatePageOffsets,代码很绕,看不懂。
大致猜测, currOffset = previous.offset + widthFactor + marginOffset。