Android ViewPager源码★★★★★

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值