一些布局源码的分析


每种布局都是继承自ViewGroup来实现的,本质上来说就是自定义View;并且它们的draw过程主要是交给子元素来实现的,因此下面仅仅简单分析各种布局的measure和layout过程(具体就是onMeasure和onLayout方法)。

LinearLayout

onMeasure

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//根据横纵方向调用两个不同的方法进行测量
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

//下面以竖直方向来说
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//...
for (int i = 0; i < count; ++i) {  
    final View child = getVirtualChildAt(i);  
    //... child为空、Gone以及分界线的情况略去
   //累计权重
    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();  
    totalWeight += lp.weight;  
    //计算
    if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {  
          //精确模式的情况下,子控件layout_height=0dp且weight大于0无法计算子控件的高度
          //但是可以先把margin值合入到总值中,后面根据剩余空间及权值再重新计算对应的高度
          final int totalLength = mTotalLength;  
          mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);  
		} else {  
			if (lp.height == 0 && lp.weight > 0) {  
			//如果这个条件成立,就代表 heightMode不是精确测量以及wrap_conent模式
			//也就是说布局是越小越好,你还想利用权值多分剩余空间是不可能的,只设为wrap_content模式
			     lp.height = LayoutParams.WRAP_CONTENT;  
			}
		
			// 子控件测量
			measureChildBeforeLayout(child, i, widthMeasureSpec,0, heightMeasureSpec,totalWeight== 0 ? mTotalLength :0);         
			//获取该子视图最终的高度,并将这个高度添加到mTotalLength中
			final int childHeight = child.getMeasuredHeight();  
			final int totalLength = mTotalLength;  
			mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); 
		} 
      //...
}

小结

需要注意的是在每次对child测量完毕后,都会调用child.getMeasuredHeight()获取该子视图最终的高度,并将这个高度添加到mTotalLength中。但是getMeasuredHeight暂时避开了lp.weight>0且高度为0子View,因为后面会将把剩余高度按weight分配给相应的子View。因此可以得出以下结论:

  • 如果我们在LinearLayout中不使用weight属性,将只进行一次measure的过程。
  • 如果使用了weight属性,LinearLayout在第一次测量时获取所有子View的高度,之后会再对所有子元素进行一次遍历,将剩余高度根据weight加到weight>0的子View上。

由此可见,weight属性对性能是有影响的。

其他说明

如果orientation是纵向的,那么LinearLayout的宽度为所有子View中最宽的宽度;高度为所有子元素高度的(子元素的高度为本身高度+上下margin)和加上LinearLayout自身上下的padding。

onLayout

@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);
    }
}

//这里仍以纵向进行分析
void layoutVertical(int left, int top, int right, int bottom) {
		...
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

						//当前子元素的LayoutParams
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            ...
            childTop += lp.topMargin;
						//这个方法调用了child的layout方法,确定了它的四个点的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
						//更新下一个子View的上边界。从这里可以看到,从上到下依次放置子元素
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

小结

  • 先判断是横向还是纵向布局。
  • 只会对所有子元素进行一次layout。如果是纵向,从上到下依次放置子元素;如果是横向,从左到右依次放置子元素。

RelativeLayout

onMeasure

@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
		if (mDirtyHierarchy) {
		    mDirtyHierarchy = false;
				//它会根据子元素之间的依赖关系对横向和纵向进行两次排序,结果分别存储在mSortedHorizontalChildren和mSortedVerticalChildren中
		    sortChildren();
		}
		...
		View[] views = mSortedHorizontalChildren;
		int count = views.length;
		for (int i = 0; i < count; i++) {
		  View child = views[i];
		  if (child.getVisibility() != GONE) {
		       LayoutParams params = (LayoutParams) child.getLayoutParams();
		       applyHorizontalSizeRules(params, myWidth);
		       measureChildHorizontal(child, params, myWidth, myHeight);
		       if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
		             offsetHorizontalAxis = true;
		       }
		  }
		}
		
		views = mSortedVerticalChildren;
		count = views.length;
		for (int i = 0; i < count; i++) {
		   View child = views[i];
		   if (child.getVisibility() != GONE) {
		         LayoutParams params = (LayoutParams) child.getLayoutParams();
		         applyVerticalSizeRules(params, myHeight);
		         measureChild(child, params, myWidth, myHeight);
		         if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
		               offsetVerticalAxis = true;
		         }
		         if (isWrapContentWidth) {
		               width = Math.max(width, params.mRight);
		         }
		         if (isWrapContentHeight) {
		               height = Math.max(height, params.mBottom);
		         }
		         if (child != ignore || verticalGravity) {
		               left = Math.min(left, params.mLeft - params.leftMargin);
		               top = Math.min(top, params.mTop - params.topMargin);
		         }
		         if (child != ignore || horizontalGravity) {
		               right = Math.max(right, params.mRight + params.rightMargin);
		               bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
		         }
		     }
		}
		//...
}

小结

  • 它先会在纵向和横向通过子元素之间的依赖关系对它们进行排序。例如:C依赖了A,A依赖了B,那么顺序就是B → A → C。
  • 分别从横向和纵向对所有子元素进行measure,也就是调用它们的measure方法。

因为RelativeLayout允许ViewB在横向上依赖ViewA,ViewA在纵向上依赖B。所以需要横向纵向分别进行一次排序测量。

简单来说就是根据子元素之间的依赖关系对它们进行两次measure。

onLayout

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    //  The layout has actually already been performed and the positions
    //  cached.  Apply the cached values to the children.
    final int count = getChildCount();

    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            RelativeLayout.LayoutParams st =
                    (RelativeLayout.LayoutParams) child.getLayoutParams();
            child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
        }
    }
}

很简单,就是通过一次遍历,调用所有子元素的layout来确定四个顶点的位置。

FrameLayout

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }

            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

小结

很简单,它先会根据子View的layout_gravity属性中水平和竖直方向的gravity确定它的左边界和上边界,接着会调用子元素的layout方法确定最终的位置。

ConstraintLayout

onMeasure

onMeasure方法看着不多,但是其中两个方法很复杂,看得有亿点头皮发麻。大概只会对所有子元素进行一次测量,其中有很多优化的代码,看得不是很懂。

onLayout

会对满足

//params是子元素的布局参数
((child.getVisibility() != 8 || params.isGuideline || params.isHelper || params.isVirtualGroup || isInEditMode) && !params.isInPlaceholder)

这些条件的子元素进行一次layout

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值