欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一些布局源码的分析

程序员文章站 2022-07-14 18:06:27
...


每种布局都是继承自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