1.ViewPager
ViewPager是一个允许用户左右翻转数据页的布局管理器,具有出色的缓存机制。
ViewPager继承自ViewGroup:
public class ViewPager extends ViewGroup {}
ViewPager有一个内部类ItemInfo,它包含了一个页面的基本信息,调用Adapter的instantiateItem方法时,在ViewPager内部就会创建这个类的对象,结构如下:
static class ItemInfo {
Object object; //页卡展示的页卡对象,也就是instantiateItem方法返回的对象
int position; // 页卡下标
boolean scrolling; // 是否正在滑动
float widthFactor; // 当前页面宽度和ViewPager宽度的比例[0-1](默认是1,可通过重写adapter的getPageWidth(int position) 方法自定义页卡宽度)这个值可以设置一个屏幕显示多少个页面
float offset; //当前页面在所有已加载的页面中的索引(用于页面布局)
}
2.ViewPager缓存处理
通常在布局文件中使用ViewPager,当Activity中setContentView方法将Xml布局设置给视图的时候,ViewPager会被初始化,按照如下方法进行初始化:
ViewPager构造方法 -> initViewPager -> onAttachedToWindow -> onMeasure -> onSizeChanged -> onLayout -> draw -> onDraw
注意:以上方法有的不止调用一次,调用整体顺序按照上述步骤。
(1)ViewPager构造方法
public ViewPager(Context context) {
super(context);
initViewPager();
}
public ViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initViewPager();
}
ViewPager在构造方法里调用了initViewPager初始化方法。
void initViewPager() {
setWillNotDraw(false);//重写onDraw需要调用,ViewGroup默认true
…
}
在初始化方法中调用了setWillNotDraw()方法,设置为false(ViewGroup中这个变量默认是true),意味着ViewPager会执行draw()的绘制方法。
初始化完成后还没有粘贴到真正的窗口Window上,所以之后会回调onAttachedToWindow方法。这个方法只是重置变量mFirstLayout。
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
}
mFirstLayout变量是一个标记位,对是否第一次执行布局操作layout进行标记。这个变量在布局方法、滚动判断和设置适配器时会用到。
接下来就是View绘制三部曲onMesure、onLayout、onDraw,由于打开了允许draw调用,所以在onDraw前还会调用draw方法。
(2)onMeasure
onMeasure方法主要做了四件事:
①对整个ViewPager的大小进行设置。设置的大小为父类传递过来的大小,也就是剩余的空间。
②测量DecorView,并对其设置大小。使用ViewPager.DecorView注释的视图被视为ViewPager“装饰”的一部分。
③通过populate()方法确保要显示的fragment已经被创建好了。
④测量Adapter的所有View,也就是每个Item,并设置其大小。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//①测量ViewPager自身大小,根据布局文件设置尺寸信息,默认大小为0(与普通自定义ViewGroup不同,普通的会先去测量子view)
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));
final int measuredWidth=getMeasuredWidth();
final int maxGutterSize = measuredWidth / 10;
mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
//ViewPager的显示区域只能显示一个view,childWidthSize和childHeightSize为一个view的宽高大小,即去除了ViewPager的内边距后的宽高
int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
//②对DecorView进行测量
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();
if (lp != null && lp.isDecor) { //是DecorView
//判断是不是设置了Gravity.LEFT或Gravity.RIGHT
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
//判断是不是设置了Gravity.TOP或Gravity.BOTTOM
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
//默认DedorView宽高是wrap_content
int widthMode=MeasureSpec.AT_MOST;
int heightMode=MeasureSpec.AT_MOST;
//记录DecorView是在垂直方向上还是在水平方向上占用空间
boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
//如果在垂直方向上占用空间,那水平方向就是match_parent,即EXACTLY,而垂直方向上具体占用多少空间,由DecorView决定,consumeHorizontal同理
if (consumeVertical) {
widthMode = MeasureSpec.EXACTLY;
} else if (consumeHorizontal) {
heightMode =MeasureSpec.EXACTLY;
}
//DecorView宽高大小,初始化为ViewPager可视区域中页卡可用空间
int widthSize = childWidthSize;
int heightSize = childHeightSize;
//如果宽度不是wrap_content,那么width的测量模式就是EXACTLY,如果宽度既不是wrap_content又不是match_parent,那说明用户在布局文件写的具体尺寸,直接将widthSize设置为这个具体尺寸
if (lp.width != LayoutParams.WRAP_CO NTENT) {
widthMode = MeasureSpec.EXACTLY;
if (lp.width != LayoutParams.FILL_PA RENT) {
widthSize = lp.width;
}
}
if (lp.height != LayoutParams.WRAP_CO NTENT) {
heightMode =MeasureSpec.EXACTLY;
if (lp.height != LayoutParams.MATCH _PARENT) {
heightSize = lp.height;
}
}
//对DecorView进行设置长和宽
final int widthSpec = MeasureSpec. makeMeasureSpec(widthSize, widthMode);
final int heightSpec = MeasureSpec. makeMeasureSpec(heightSize, heightMode);
child.measure(widthSpec, heightSpec);
//如果DecorView占用了ViewPager的垂直方向的空间,需要将页卡的竖直方向可用的空间减去DecorView的高度,水平方向上同理
if (consumeVertical) {
childHeightSize -= child.getMeasuredHeight();
} else if (consumeHorizontal) {
childWidthSize -= child.getMeasuredWidth();
}
}
}
}
//ViewPager剩余的宽度MeasureSpec
mChildWidthMeasureSpec = MeasureSpec. makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
//ViewPager剩余的高度MeasureSpec
mChildHeightMeasureSpec = MeasureSpec. makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
//③确保要显示的fragment已经被创建好了。mInLayout标记是为了在计算排列子View的时候避免与添加和删除子View产生冲突
mInLayout = true;
populate(); //ViewPager原理的核心关键方法
mInLayout = false;
//④开始遍历设置每个item的宽高
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) {
//生成每个item宽的MeasureSpec。(childWidthSize * lp.widthFactor)表示当前页卡的实际宽度
final int widthSpec = MeasureSpec. makeMeasureSpec((int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
//对每个item的宽高进行测量
child.measure(widthSpec, mChildHeightMeasureSpec);
}
}
}
}
(3)onSizeChanged
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw) {
//如果两值不同,就重新计算滚动位置
recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
}
}
完成size改变的方法后,会触发onLayout执行操作,这是三部曲里边的布局方法。
(4)onLayout
onLayout主要做了三件事:
①设置Decor View的位置。先layout DecorView,它会占用一定空间,计算出四个padding值 提供给后面layout普通子View使用;
②设置普通View的位置。利用第一步得出的padding值得到一个可用的空间对子View进行布局,这里就涉及到对多个子View横向排列顺序的问题,这里就根据ItemInfo中的offset值来决定的,通过offset计算每个子View的Left值。
③将页面移动到第一个Item位置
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;
//①设置DecorView的位置
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) {
final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
//根据水平方向上的Gravity,确定childLeft的值
switch (hgrav) {
default:
childLeft = paddingLeft;
break;
case Gravity.LEFT:
childLeft = paddingLeft;
//累加左内边距(多个DecorView都居左边,肯定要累加啦
paddingLeft += child.getMeasuredWidth();
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft);
break;
case Gravity.RIGHT:
//计算居右侧时的左边距=(viewPager可见宽-右边距-child测量宽)
childLeft = width - paddingRight - child.getMeasuredWidth();
//累加右内边距
paddingRight += child.getMeasuredWidth();
break;
}
//与上面水平方向的同理,据水平方向上的Gravity,确定childTop的值
switch (vgrav) {
default:
childTop = paddingTop;
break;
case Gravity.TOP:
childTop = paddingTop;
paddingTop += child.getMeasuredHeight();
break;
case Gravity.CENTER_VERTICAL:
childTop = Math.max((height - child.getMeasuredHeight()) / 2, paddingTop);
break;
case Gravity.BOTTOM:
childTop = height - paddingBottom - child.getMeasuredHeight();
paddingBottom += child.getMeasuredHeight();
break;
}
//上面计算的childLeft是相对ViewPager的左边计算的, 还需要加上x方向已经滑动的距离scrollX
childLeft += scrollX;
//确定DecorView的位置
child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
decorCount++; /DecorView的计数器加1
}
}
}
//普通页面(Adapter的View)可用宽度
final int childWidth = width - paddingLeft - paddingRight;
//②设置非Decor View的位置
//下面针对普通页面布局,在此onLayout之前已经得到正确的偏移量offset了
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
ItemInfo ii;
//如果子View不是Deco