Reprinted from: https://www.jianshu.com/p/b0830f9b44bb
We know that the ViewPager is a ViewGroup. When we customize the ViewGroup ourselves, in addition to writing at least two constructors, the onMeasure and onLayout functions basically have to be written. Today, I will study two functions of onMeasure and onLayout carefully~
onMeasure
ViewPager divides sub views into two types. One is Decor View used to decorate ViewPager, which may take up some space; the other is common sub View, that is, each View displayed when we slide horizontally. onMeasure first measures the decor View, and then the common sub View. The detailed notes are as follows:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Set the size information according to the layout file. The default size is 0 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); final int measuredWidth = getMeasuredWidth(); final int maxGutterSize = measuredWidth / 10; //Set the value of mGutterSize, which will be discussed later mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize); // The display area of ViewPager can only be displayed for one View //childWidthSize and childHeightSize are the available width height sizes of a View //That is, the width and height after the internal margin of ViewPager are removed int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight(); int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); //1. Measure Decor View first //The following loop is only for Decor View, that is, View used to decorate ViewPager 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(); //1.1 if the View is Decor View, it is used to decorate the View pager if (lp != null && lp.isDecor) { //1.2 get the Gravity in horizontal direction and vertical direction of Decor View final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; //1.3 the width and height corresponding to the default Dedor View mode is wrap < content int widthMode = MeasureSpec.AT_MOST; int heightMode = MeasureSpec.AT_MOST; //1.4 record whether Decor View occupies space vertically or horizontally boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM; boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT; //1.5 consumeHorizontal: if the space is occupied in the vertical direction, // Then the horizontal direction is match [parent, i.e. EXACTLY //How much space is occupied in the vertical direction depends on Decor View //The same goes for consumeHorizontal if (consumeVertical) { widthMode = MeasureSpec.EXACTLY; } else if (consumeHorizontal) { heightMode = MeasureSpec.EXACTLY; } //1.6 width and height, initialized as the available space of sub View in ViewPager visual area int widthSize = childWidthSize; int heightSize = childHeightSize; //1.7 if width is not wrap [content], the measurement mode of width is EXACTLY //If the width is neither wrap content nor match parent, then it is the user //Set the widthSize to the specific size written in the layout file if (lp.width != LayoutParams.WRAP_CONTENT) { widthMode = MeasureSpec.EXACTLY; if (lp.width != LayoutParams.FILL_PARENT) { widthSize = lp.width; } } //1.8 is the same as 1.7 if (lp.height != LayoutParams.WRAP_CONTENT) { heightMode = MeasureSpec.EXACTLY; if (lp.height != LayoutParams.FILL_PARENT) { heightSize = lp.height; } } //1.9 width height specification of composite Decor View (integer including size and mode) final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode); final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode); //1.10 measure DecorView child.measure(widthSpec, heightSpec); //1.11 if Decor View occupies the space in the vertical direction of ViewPager //You need to subtract the height of DecorView from the space available in the vertical direction of the child View, //In the same way, do the same in the horizontal direction if (consumeVertical) { childHeightSize -= child.getMeasuredHeight(); } else if (consumeHorizontal) { childWidthSize -= child.getMeasuredWidth(); } } } } //2. specification of default width and height of subview (including integer of size and mode) //(PS: mChildWidthMeasureSpec is not used again, I feel it is a bit redundant) mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); //3. Make sure that the fragment s we need to display have been created by us //populate() is more complex, which will be described in detail later mInLayout = true; populate(); mInLayout = false; // 4. Measure the sub View again size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); //4.1 no need to measure if visibility is fine if (child.getVisibility() != GONE) { if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec); //4.2 get LayoutParams of child View final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //4.3 only measure for sub View instead of Decor View if (lp == null || !lp.isDecor) { //4.4 the widthFactor of layoutparams is a floating-point number with a value of [0,1], // Used to indicate the ratio of subview to the available width of subview in the ViewPager display area, // That is, (childWidthSize * lp.widthFactor) represents the actual width of the current subview final int widthSpec = MeasureSpec.makeMeasureSpec( (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY); //4.5 measure the current subview child.measure(widthSpec, mChildHeightMeasureSpec); } } } }onLayout
We know that the child views of ViewPager are placed horizontally, so in onLayout, most of the work is to calculate childLeft, that is, the left position of child views, while the top position is basically the same, which can be reflected in the following code:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //1. The following local variables are very simple and will not be explained 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(); //2. Number of decor views int decorCount = 0; //3. First, layout the Decor View, and then layout the common sub View, // The reason why the Decor View is laid out first is to make the normal sub View have appropriate offset //The following loop is mainly for Decor View for (int i = 0; i < count; i++) { final View child = getChildAt(i); //3.1 layout only when visibility is not GONE if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //3.2 the margins on the left and top are initialized to 0 int childLeft = 0; int childTop = 0; if (lp.isDecor) {//3.3 Decor View only //3.4 get Gravity in horizontal or vertical direction final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; //3.5 determine childLeft and paddingRight according to Gravity in horizontal direction switch (hgrav) { default://When the horizontal direction Gravity is not set (left, middle and right), childLeft takes paddingLeft childLeft = paddingLeft; break; case Gravity.LEFT://Horizontal Gravity is left, Decor View is to the left childLeft = paddingLeft; paddingLeft += child.getMeasuredWidth(); break; case Gravity.CENTER_HORIZONTAL://Center Decor View childLeft = Math.max((width - child.getMeasuredWidth()) / 2, paddingLeft); break; case Gravity.RIGHT://Move Decor View to the far right childLeft = width - paddingRight - child.getMeasuredWidth(); paddingRight += child.getMeasuredWidth(); break; } //3.6 is the same as 3.5 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; } //3.7 the childLeft calculated above is relative to the left side of ViewPager, //You also need to add the distance scrollX that has slipped in the x direction childLeft += scrollX; //3.8 layout of Decor View child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); //3.9 add Decor View quantity + 1 decorCount++; } } } //4. Width of common subview final int childWidth = width - paddingLeft - paddingRight; // Page views. Do this once we have the right padding offsets from above. //5. For the normal sub View layout, we have got the correct offset before that for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //5.1 ItemInfo is a static internal class of ViewPager, // It stores the position, offset and other information of common subview, which is an abstract description of common subview ItemInfo ii; //5.2 infoForChild queries the corresponding ItemInfo object through the incoming View if (!lp.isDecor && (ii = infoForChild(child)) != null) { //Calculate the left offset of the current subview int loff = (int) (childWidth * ii.offset); //Left margin + left offset to get the left position of the final subview int childLeft = paddingLeft + loff; int childTop = paddingTop; //5.3 if the current subview needs to measure, when the subview is newly added during Layout, // Then this sub View needs to be measured, that is, needsMeasure is true if (lp.needsMeasure) { //5.4 marking has been measured lp.needsMeasure = false; //5.5 the following process is similar to onMeasure 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()); //5.6 layout common subviews child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } //6. Save some local variables to instance variables mTopPageBounds = paddingTop; mBottomPageBounds = height - paddingBottom; mDecorChildCount = decorCount; //7. If it is the first layout, slide the ViewPager to the position of the first child View if (mFirstLayout) { scrollToItem(mCurItem, false, 0, false); } //8. The mark has been laid out, that is, it is no longer the first time mFirstLayout = false; }OK, the most basic onMeasure and onLayout have been analyzed, and they can't slide now. Next, I will analyze the slide of ViewPager~