LinearLayout源码简单查看

xiaoxiao2021-02-28  121

 
绘制过程

 view的绘制过程无非是onmeasure(),onLayout(),onDraw()。LinearLayout的三个过程:

1,通过weight, gravity,测量模式 测量view和子view的大小。  2,通过 gravity确定子view的位置 

3,通过 在onDraw绘制divider

急需反馈,希望大家多少给点建议。能进一步改进。

接下来我们一起看下onMeasure()方法:

/** * 测量方法 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //竖向 if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { //横向 measureHorizontal(widthMeasureSpec, heightMeasureSpec); } } 接下来我们看下里面的实现方法: /** * 竖向测量 * Measures the children when the orientation of this LinearLayout is set * to {@link #VERTICAL}. * * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. * @param heightMeasureSpec Vertical space requirements as imposed by the parent. * * @see #getOrientation() * @see #setOrientation(int) * @see #onMeasure(int, int) */ void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { //内部使用的高度 mTotalLength = 0; //宽 int maxWidth = 0; //view状态 int childState = 0; //改变本地最大宽度 int alternativeMaxWidth = 0; //权重 的最大宽 int weightedMaxWidth = 0; //全部填充父布局 boolean allFillParent = true; //权重总和 float totalWeight = 0; //子view的数量 final int count = getVirtualChildCount(); //得到宽高的测量模式 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); //填充宽 boolean matchWidth = false; //跳过测量 boolean skippedMeasure = false; //基线下标 final int baselineChildIndex = mBaselineAlignedChildIndex; //最大的view final boolean useLargestChild = mUseLargestChild; //最大的高设置为Integer的最小值 int largestChildHeight = Integer.MIN_VALUE; // See how tall everyone is. Also remember max width. //遍历子view for (int i = 0; i < count; ++i) { //一个一个取出view final android.view.View child = getVirtualChildAt(i); //如果子View是null就继续测量下一个子View if (child == null) { mTotalLength += measureNullChild(i); continue; } //如果子View是GONE的也不算在总高度里面,这里也能看出GONE和INVISIBLE的区别 if (child.getVisibility() == android.view.View.GONE) { i += getChildrenSkipCount(child, i); continue; } //如果有分割线,就把分割线高度加上 if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerHeight; } //得到子view的LayoutParams,这里就把他的LayoutParams强制转换了 android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); //计入总权重 totalWeight += lp.weight; //当LinearLayout是EXACTLY模式,且子view的高度为0, //只有有剩余空间的时候,才回根据权重再次测量 if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { // Optimization: don't bother measuring children who are going to use // leftover space. These views will get measured again down below if // there is any leftover space. final int totalLength = mTotalLength; //加上child的margin属性,跳过测量 mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { int oldHeight = Integer.MIN_VALUE; if (lp.height == 0 && lp.weight > 0) { // heightMode is either UNSPECIFIED or AT_MOST, and this // child wanted to stretch to fill available space. // Translate that to WRAP_CONTENT so that it does not end up // with a height of 0 //当前模式有两种可能:UNSPECIFIED和AT_MOST。 //设置为WRAP_CONTENT,以保证不会为0 oldHeight = 0; lp.height = android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; } // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if needed). //开始测量,如果有了权重,那么优先把所有空间给与它,如果有需要,以后也会缩小 measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); //重置高度 if (oldHeight != Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; //加上margin mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); } } /** * If applicable, compute the additional offset to the child's baseline * we'll need later when asked {@link #getBaseline}. */ if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) { mBaselineChildTop = mTotalLength; } // if we are trying to use a child index for our baseline, the above // book keeping only works if there are no children above it with // weight. fail fast to aid the developer. if (i < baselineChildIndex && lp.weight > 0) { /*一个线性布局的子目录小于mBaselineAlignedChildIndex,它的权重为0,这是行不通的。要么去掉重量,要么不设置mBaselineAlignedChildIndex。*/ throw new RuntimeException("A child of LinearLayout with index " + "less than mBaselineAlignedChildIndex has weight > 0, which " + "won't work. Either remove the weight, or don't set " + "mBaselineAlignedChildIndex."); } boolean matchWidthLocally = false; //这种情况下相当于布局撑满当前布局,所以可以跳过测量 if (widthMode != MeasureSpec.EXACTLY && lp.width == android.widget.LinearLayout.LayoutParams.MATCH_PARENT) { // The width of the linear layout will scale, and at least one // child said it wanted to match our width. Set a flag // indicating that we need to remeasure at least that view when // we know our width. matchWidth = true; matchWidthLocally = true; } //得到子view的宽 final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; maxWidth = Math.max(maxWidth, measuredWidth); childState = combineMeasuredStates(childState, child.getMeasuredState()); allFillParent = allFillParent && lp.width == android.widget.LinearLayout.LayoutParams.MATCH_PARENT; //如果weight大于0,我们需要重新测量 if (lp.weight > 0) { /* * Widths of weighted Views are bogus if we end up * remeasuring, so keep them separate. */ weightedMaxWidth = Math.max(weightedMaxWidth, matchWidthLocally ? margin : measuredWidth); } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); } i += getChildrenSkipCount(child, i); } //如果测量的高度大于0,且当前view前需要绘制divider,需加上divider的值 if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerHeight; } //如果使用最大的view,模式为AT_MOST和UNSPECIFIED if (useLargestChild && (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final android.view.View child = getVirtualChildAt(i); //child为null,直接跳过 if (child == null) { mTotalLength += measureNullChild(i); continue; } //视图不显示,跳过 if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); // Account for negative margins final int totalLength = mTotalLength; //加上margin和偏移量 mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } } // 加上padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // 和我们的最小高度对比 heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // 将我们计算的尺寸与测量的尺寸进行协调 int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); heightSize = heightSizeAndState & MEASURED_SIZE_MASK; // Either expand children with weight to take up available space or // shrink them if they extend beyond our current bounds. If we skipped // measurement on any children, we need to measure them now. int delta = heightSize - mTotalLength; if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; mTotalLength = 0; for (int i = 0; i < count; ++i) { final android.view.View child = getVirtualChildAt(i); if (child.getVisibility() == android.view.View.GONE) { continue; } android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; //权重大于0,开始计算 if (childExtra > 0) { // Child said it could absorb extra space -- give him his share int share = (int) (childExtra * delta / weightSum); weightSum -= childExtra; delta -= share; final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width); // TODO: Use a field like lp.isMeasured to figure out if this // child has been previously measured if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) { // child was measured once already above... // base new measurement on stored values int childHeight = child.getMeasuredHeight() + share; if (childHeight < 0) { childHeight = 0; } child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); } else { // child was skipped in the loop above. // Measure for this first time here child.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, MeasureSpec.EXACTLY)); } // Child may now not fit in vertical dimension. childState = combineMeasuredStates(childState, child.getMeasuredState() & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT)); } final int margin = lp.leftMargin + lp.rightMargin; final int measuredWidth = child.getMeasuredWidth() + margin; maxWidth = Math.max(maxWidth, measuredWidth); boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY && lp.width == android.widget.LinearLayout.LayoutParams.MATCH_PARENT; alternativeMaxWidth = Math.max(alternativeMaxWidth, matchWidthLocally ? margin : measuredWidth); allFillParent = allFillParent && lp.width == android.widget.LinearLayout.LayoutParams.MATCH_PARENT; final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } // 加上padding mTotalLength += mPaddingTop + mPaddingBottom; // TODO: Should we recompute the heightSpec based on the new total length? } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth); // We have no limit, so make all weighted views as tall as the largest child. // Children will have already been measured once. if (useLargestChild && heightMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final android.view.View child = getVirtualChildAt(i); if (child == null || child.getVisibility() == android.view.View.GONE) { continue; } final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { child.measure( MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(largestChildHeight, MeasureSpec.EXACTLY)); } } } } if (!allFillParent && widthMode != MeasureSpec.EXACTLY) { maxWidth = alternativeMaxWidth; } maxWidth += mPaddingLeft + mPaddingRight; // Check against our minimum width maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); //如果其中有一个match_width,则强迫所有的同意宽度 if (matchWidth) { forceUniformWidth(count, heightMeasureSpec); } } /** * 横向测量 ------和竖向基本类似,自己 研究一下 * Measures the children when the orientation of this LinearLayout is set * to {@link #HORIZONTAL}. * * @param widthMeasureSpec Horizontal space requirements as imposed by the parent. * @param heightMeasureSpec Vertical space requirements as imposed by the parent. * * @see #getOrientation() * @see #setOrientation(int) * @see #onMeasure(int, int) */ void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; int maxHeight = 0; int childState = 0; int alternativeMaxHeight = 0; int weightedMaxHeight = 0; boolean allFillParent = true; float totalWeight = 0; final int count = getVirtualChildCount(); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); boolean matchHeight = false; boolean skippedMeasure = false; if (mMaxAscent == null || mMaxDescent == null) { mMaxAscent = new int[VERTICAL_GRAVITY_COUNT]; mMaxDescent = new int[VERTICAL_GRAVITY_COUNT]; } final int[] maxAscent = mMaxAscent; final int[] maxDescent = mMaxDescent; maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; final boolean baselineAligned = mBaselineAligned; final boolean useLargestChild = mUseLargestChild; final boolean isExactly = widthMode == MeasureSpec.EXACTLY; int largestChildWidth = Integer.MIN_VALUE; // See how wide everyone is. Also remember max height. for (int i = 0; i < count; ++i) { final android.view.View child = getVirtualChildAt(i); if (child == null) { mTotalLength += measureNullChild(i); continue; } if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } if (hasDividerBeforeChildAt(i)) { mTotalLength += mDividerWidth; } final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); totalWeight += lp.weight; if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) { // Optimization: don't bother measuring children who are going to use // leftover space. These views will get measured again down below if // there is any leftover space. if (isExactly) { mTotalLength += lp.leftMargin + lp.rightMargin; } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin); } // Baseline alignment requires to measure widgets to obtain the // baseline offset (in particular for TextViews). The following // defeats the optimization mentioned above. Allow the child to // use as much space as it wants because we can shrink things // later (and re-measure). if (baselineAligned) { final int freeWidthSpec = MeasureSpec.makeSafeMeasureSpec( MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.UNSPECIFIED); final int freeHeightSpec = MeasureSpec.makeSafeMeasureSpec( MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED); child.measure(freeWidthSpec, freeHeightSpec); } else { skippedMeasure = true; } } else { int oldWidth = Integer.MIN_VALUE; if (lp.width == 0 && lp.weight > 0) { // widthMode is either UNSPECIFIED or AT_MOST, and this // child // wanted to stretch to fill available space. Translate that to // WRAP_CONTENT so that it does not end up with a width of 0 oldWidth = 0; lp.width = android.widget.LinearLayout.LayoutParams.WRAP_CONTENT; } // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if needed). measureChildBeforeLayout(child, i, widthMeasureSpec, totalWeight == 0 ? mTotalLength : 0, heightMeasureSpec, 0); if (oldWidth != Integer.MIN_VALUE) { lp.width = oldWidth; } final int childWidth = child.getMeasuredWidth(); if (isExactly) { mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } if (useLargestChild) { largestChildWidth = Math.max(childWidth, largestChildWidth); } } boolean matchHeightLocally = false; if (heightMode != MeasureSpec.EXACTLY && lp.height == android.widget.LinearLayout.LayoutParams.MATCH_PARENT) { // The height of the linear layout will scale, and at least one // child said it wanted to match our height. Set a flag indicating that // we need to remeasure at least that view when we know our height. matchHeight = true; matchHeightLocally = true; } final int margin = lp.topMargin + lp.bottomMargin; final int childHeight = child.getMeasuredHeight() + margin; childState = combineMeasuredStates(childState, child.getMeasuredState()); if (baselineAligned) { final int childBaseline = child.getBaseline(); if (childBaseline != -1) { // Translates the child's vertical gravity into an index // in the range 0..VERTICAL_GRAVITY_COUNT final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) & Gravity.VERTICAL_GRAVITY_MASK; final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) & ~Gravity.AXIS_SPECIFIED) >> 1; maxAscent[index] = Math.max(maxAscent[index], childBaseline); maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline); } } maxHeight = Math.max(maxHeight, childHeight); allFillParent = allFillParent && lp.height == android.widget.LinearLayout.LayoutParams.MATCH_PARENT; if (lp.weight > 0) { /* * Heights of weighted Views are bogus if we end up * remeasuring, so keep them separate. */ weightedMaxHeight = Math.max(weightedMaxHeight, matchHeightLocally ? margin : childHeight); } else { alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight); } i += getChildrenSkipCount(child, i); } if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) { mTotalLength += mDividerWidth; } // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, // the most common case if (maxAscent[INDEX_TOP] != -1 || maxAscent[INDEX_CENTER_VERTICAL] != -1 || maxAscent[INDEX_BOTTOM] != -1 || maxAscent[INDEX_FILL] != -1) { final int ascent = Math.max(maxAscent[INDEX_FILL], Math.max(maxAscent[INDEX_CENTER_VERTICAL], Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); final int descent = Math.max(maxDescent[INDEX_FILL], Math.max(maxDescent[INDEX_CENTER_VERTICAL], Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); maxHeight = Math.max(maxHeight, ascent + descent); } if (useLargestChild && (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) { mTotalLength = 0; for (int i = 0; i < count; ++i) { final android.view.View child = getVirtualChildAt(i); if (child == null) { mTotalLength += measureNullChild(i); continue; } if (child.getVisibility() == GONE) { i += getChildrenSkipCount(child, i); continue; } final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); if (isExactly) { mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } } // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; int widthSize = mTotalLength; // Check against our minimum width widthSize = Math.max(widthSize, getSuggestedMinimumWidth()); // Reconcile our calculated size with the widthMeasureSpec int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0); widthSize = widthSizeAndState & MEASURED_SIZE_MASK; // Either expand children with weight to take up available space or // shrink them if they extend beyond our current bounds. If we skipped // measurement on any children, we need to measure them now. int delta = widthSize - mTotalLength; if (skippedMeasure || delta != 0 && totalWeight > 0.0f) { float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight; maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1; maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1; maxHeight = -1; mTotalLength = 0; for (int i = 0; i < count; ++i) { final android.view.View child = getVirtualChildAt(i); if (child == null || child.getVisibility() == android.view.View.GONE) { continue; } final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { // Child said it could absorb extra space -- give him his share int share = (int) (childExtra * delta / weightSum); weightSum -= childExtra; delta -= share; final int childHeightMeasureSpec = getChildMeasureSpec( heightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin, lp.height); // TODO: Use a field like lp.isMeasured to figure out if this // child has been previously measured if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) { // child was measured once already above ... base new measurement // on stored values int childWidth = child.getMeasuredWidth() + share; if (childWidth < 0) { childWidth = 0; } child.measure( MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), childHeightMeasureSpec); } else { // child was skipped in the loop above. Measure for this first time here child.measure(MeasureSpec.makeMeasureSpec( share > 0 ? share : 0, MeasureSpec.EXACTLY), childHeightMeasureSpec); } // Child may now not fit in horizontal dimension. childState = combineMeasuredStates(childState, child.getMeasuredState() & MEASURED_STATE_MASK); } if (isExactly) { mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child); } else { final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && lp.height == android.widget.LinearLayout.LayoutParams.MATCH_PARENT; final int margin = lp.topMargin + lp .bottomMargin; int childHeight = child.getMeasuredHeight() + margin; maxHeight = Math.max(maxHeight, childHeight); alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight); allFillParent = allFillParent && lp.height == android.widget.LinearLayout.LayoutParams.MATCH_PARENT; if (baselineAligned) { final int childBaseline = child.getBaseline(); if (childBaseline != -1) { // Translates the child's vertical gravity into an index in the range 0..2 final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) & Gravity.VERTICAL_GRAVITY_MASK; final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) & ~Gravity.AXIS_SPECIFIED) >> 1; maxAscent[index] = Math.max(maxAscent[index], childBaseline); maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline); } } } // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; // TODO: Should we update widthSize with the new total length? // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, // the most common case if (maxAscent[INDEX_TOP] != -1 || maxAscent[INDEX_CENTER_VERTICAL] != -1 || maxAscent[INDEX_BOTTOM] != -1 || maxAscent[INDEX_FILL] != -1) { final int ascent = Math.max(maxAscent[INDEX_FILL], Math.max(maxAscent[INDEX_CENTER_VERTICAL], Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM]))); final int descent = Math.max(maxDescent[INDEX_FILL], Math.max(maxDescent[INDEX_CENTER_VERTICAL], Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM]))); maxHeight = Math.max(maxHeight, ascent + descent); } } else { alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight); // We have no limit, so make all weighted views as wide as the largest child. // Children will have already been measured once. if (useLargestChild && widthMode != MeasureSpec.EXACTLY) { for (int i = 0; i < count; i++) { final android.view.View child = getVirtualChildAt(i); if (child == null || child.getVisibility() == android.view.View.GONE) { continue; } final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); float childExtra = lp.weight; if (childExtra > 0) { child.measure( MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY)); } } } } if (!allFillParent && heightMode != MeasureSpec.EXACTLY) { maxHeight = alternativeMaxHeight; } maxHeight += mPaddingTop + mPaddingBottom; // Check against our minimum height maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK), resolveSizeAndState(maxHeight, heightMeasureSpec, (childState< 测量完了,我们就应该决定在哪画我们的view,可以看出,我们依然跟measure过程是一样的,分为Horizontal和Vertical。 /** * 定位 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }

接下来看看里面怎么实现的:

/** * 竖向定位子view的位置 * Position the children during a layout pass if the orientation of this * LinearLayout is set to {@link #VERTICAL}. * * @see #getOrientation() * @see #setOrientation(int) * @see #onLayout(boolean, int, int, int, int) * @param left * @param top * @param right * @param bottom */ void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go final int width = right - left; int childRight = width - mPaddingRight; // Space available for child int childSpace = width - paddingLeft - mPaddingRight; final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; //通过gravity 初步计算childTop switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } //遍历view for (int i = 0; i < count; i++) { final android.view.View child = getVirtualChildAt(i); //如果child为null,不做变化 if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { //如果该view不为GONE //得到child测量后的真是宽高 final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); //以下就是根据gravity值,计算childLeft的值。 int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; } //如果child前边应该有分隔符,加上分隔符的height if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } //加上margin值 childTop += lp.topMargin; //设置child的位置。根据top,left,width,height //内部调用child.layout()方法 setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } } /** * 横向 整个流程类似于竖向 * Position the children during a layout pass if the orientation of this * LinearLayout is set to {@link #HORIZONTAL}. * * @see #getOrientation() * @see #setOrientation(int) * @see #onLayout(boolean, int, int, int, int) * @param left * @param top * @param right * @param bottom */ void layoutHorizontal(int left, int top, int right, int bottom) { final boolean isLayoutRtl = isLayoutRtl(); final int paddingTop = mPaddingTop; int childTop; int childLeft; // Where bottom of child should go final int height = bottom - top; int childBottom = height - mPaddingBottom; // Space available for child int childSpace = height - paddingTop - mPaddingBottom; final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final boolean baselineAligned = mBaselineAligned; final int[] maxAscent = mMaxAscent; final int[] maxDescent = mMaxDescent; final int layoutDirection = getLayoutDirection(); switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) { case Gravity.RIGHT: // mTotalLength contains the padding already childLeft = mPaddingLeft + right - left - mTotalLength; break; case Gravity.CENTER_HORIZONTAL: // mTotalLength contains the padding already childLeft = mPaddingLeft + (right - left - mTotalLength) / 2; break; case Gravity.LEFT: default: childLeft = mPaddingLeft; break; } int start = 0; int dir = 1; //In case of RTL, start drawing from the last child. if (isLayoutRtl) { start = count - 1; dir = -1; } for (int i = 0; i < count; i++) { int childIndex = start + dir * i; final android.view.View child = getVirtualChildAt(childIndex); if (child == null) { childLeft += measureNullChild(childIndex); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); int childBaseline = -1; final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); if (baselineAligned && lp.height != android.widget.LinearLayout.LayoutParams.MATCH_PARENT) { childBaseline = child.getBaseline(); } int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: childTop = paddingTop + lp.topMargin; if (childBaseline != -1) { childTop += maxAscent[INDEX_TOP] - childBaseline; } break; case Gravity.CENTER_VERTICAL: // Removed support for baseline alignment when layout_gravity or // gravity == center_vertical. See bug #1038483. // Keep the code around if we need to re-enable this feature // if (childBaseline != -1) { // // Align baselines vertically only if the child is smaller than us // if (childSpace - childHeight > 0) { // childTop = paddingTop + (childSpace / 2) - childBaseline; // } else { // childTop = paddingTop + (childSpace - childHeight) / 2; // } // } else { childTop = paddingTop + ((childSpace - childHeight) / 2) + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = childBottom - childHeight - lp.bottomMargin; if (childBaseline != -1) { int descent = child.getMeasuredHeight() - childBaseline; childTop -= (maxDescent[INDEX_BOTTOM] - descent); } break; default: childTop = paddingTop; break; } if (hasDividerBeforeChildAt(childIndex)) { childLeft += mDividerWidth; } childLeft += lp.leftMargin; setChildFrame(child, childLeft + getLocationOffset(child), childTop, childWidth, childHeight); childLeft += childWidth + lp.rightMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, childIndex); } } }

大小确定了,位置确定了,接下来就可以绘制了。依然是分为Horizontal和Vertical。

/** * view绘制 * @param canvas */ @Override protected void onDraw(Canvas canvas) { //分隔符为空,不往下执行 if (mDivider == null) { return; } //不同方向,不同绘制方向 if (mOrientation == VERTICAL) { drawDividersVertical(canvas); } else { drawDividersHorizontal(canvas); } }

看实现:

/** * 竖向绘制 * @param canvas */ void drawDividersVertical(Canvas canvas) { //获取 竖向子view个数 final int count = getVirtualChildCount(); //遍历 for (int i = 0; i < count; i++) { //通过下标,得到每个子view final android.view.View child = getVirtualChildAt(i); //子view状态为显示,则开始绘制(从这可以看出来,GONE不绘制,不占用资源) if (child != null && child.getVisibility() != GONE) { //决定在哪放置子view的分隔符,返回true表示在之前应该有个分隔符。则绘制。 if (hasDividerBeforeChildAt(i)) { final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); final int top = child.getTop() - lp.topMargin - mDividerHeight; //以该view的top绘制分隔符 drawHorizontalDivider(canvas, top); } } } //如果是最后一个view,则以布局的bottom为基准,绘制 //绘制divider if (hasDividerBeforeChildAt(count)) { //得到最后一个显示的view final android.view.View child = getLastNonGoneChild(); int bottom = 0; if (child == null) { bottom = getHeight() - getPaddingBottom() - mDividerHeight; } else { final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); bottom = child.getBottom() + lp.bottomMargin; } /*绘制分隔符*/ drawHorizontalDivider(canvas, bottom); } } /** * 绘制横向分隔符 * @param canvas */ void drawDividersHorizontal(Canvas canvas) { /*得到竖向布局的总个数*/ final int count = getVirtualChildCount(); //在当前view的右还有左边绘制分隔符 final boolean isLayoutRtl = isLayoutRtl(); for (int i = 0; i < count; i++) { final android.view.View child = getVirtualChildAt(i); if (child != null && child.getVisibility() != GONE) { //决定在哪放置子view的分隔符,返回true表示在之前应该有个分隔符。则绘制。 if (hasDividerBeforeChildAt(i)) { final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); final int position; if (isLayoutRtl) { position = child.getRight() + lp.rightMargin; } else { position = child.getLeft() - lp.leftMargin - mDividerWidth; } //绘制分隔符 drawVerticalDivider(canvas, position); } } } //决定在哪放置子view的分隔符,返回true表示在之前应该有个分隔符。则绘制。 if (hasDividerBeforeChildAt(count)) { final android.view.View child = getLastNonGoneChild(); int position; if (child == null) { if (isLayoutRtl) { position = getPaddingLeft(); } else { position = getWidth() - getPaddingRight() - mDividerWidth; } } else { final android.widget.LinearLayout.LayoutParams lp = (android.widget.LinearLayout.LayoutParams) child.getLayoutParams(); if (isLayoutRtl) { position = child.getLeft() - lp.leftMargin - mDividerWidth; } else { position = child.getRight() + lp.rightMargin; } } drawVerticalDivider(canvas, position); } }

技术水平有限,等待大家评论纠错。整篇源码已放在github。

LinearLayout源码

转载请注明原文地址: https://www.6miu.com/read-41653.html

最新回复(0)