【自定义view系列】View的layout过程

549人阅读 评论(0) 收藏 举报
分类:

  layout即确定view的位置,知道view的位置。

这里写图片描述

  通过debug的栈帧来明确源码方法的调用是阅读源码非常重要的工具,这里还是通过栈帧来分析view的layout过程。

一.栈帧

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.ht.androidstudy.view.DebugTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="dddd"
        />


</LinearLayout>

自定义view

public class DebugTextView extends TextView {

    public DebugTextView(Context context) {
        super(context);
    }

    public DebugTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("dd", "");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.d("dd", "");
        super.onLayout(changed, left, top, right, bottom);
    }
}

在onLayout方法的Log.d(“dd”, “”);这一行打上断点,我们可以看到此时的栈帧:

这里写图片描述

二.栈帧分析

  这里我们看到onLayout也是从performTraversals开始的。这里我们抛开系统层面的影响,只分析我们的布局,即根布局LinearLayout和子View DebugTextView。

1.LinearLayout

  首先调用LinearLayout的layout方法,layout方法在View中,具体源码如下:

    public final void layout(int l, int t, int r, int b) {
        boolean changed = setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
            }

            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~LAYOUT_REQUIRED;
        }
        mPrivateFlags &= ~FORCE_LAYOUT;
    }

  layout的大致流程如下:首先会通过setFrame方法来设定View的四个顶点的位置,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了,接着会调用LinearLayout onLayout方法,这个方法的源码如下:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical();
        } else {
            layoutHorizontal();
        }
    }

  onLayout方法的用途是父容器确定子元素的位置,和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关,所以View和ViewGroup均没有真正实现onLayout方法。这里我们看一下LinearLayout的onLayout方法,具体看一下layoutVertical方法的实现,源码如下:

  void layoutVertical() {
        final int paddingLeft = mPaddingLeft;

        int childTop = mPaddingTop;
        int childLeft;

        // Where right end of child should go
        final int width = mRight - mLeft;
        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.HORIZONTAL_GRAVITY_MASK;

        if (majorGravity != Gravity.TOP) {
           switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already, we add the top
                   // padding to compensate
                   childTop = mBottom - mTop + mPaddingTop - mTotalLength;
                   break;

               case Gravity.CENTER_VERTICAL:
                   childTop += ((mBottom - mTop)  - mTotalLength) / 2;
                   break;
           }

        }

        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();

                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();

                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }

                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.LEFT:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;

                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;
                    default:
                        childLeft = paddingLeft;
                        break;
                }


                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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

  这里来分析一下layoutVertical的代码逻辑,可以看到,此方法会遍历所有子元素并调用setChildFrame方法来为子元素制定对应的位置,其中childTop会逐渐增大,这就意味着后面的子元素会被放置在靠下的位置,这刚好符合竖直方向的LinearLayout的特性。至于setChildFrame,它仅仅是调用子元素的layout方法而已,这样父元素在layout方法中完成自己的定位以后,就通过onLayout方法去调用子元素的layout方法,子元素又会通过自己的layout方法来确定自己的位置,这样就一层一层的传递下去就完成了整个View树的layout过程。setChildFrame方法实现如下:

    private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
    }

  我们注意到,setChildFrame的width和height实际上就是子元素的测量宽高,从下面的代码可以看出这一点:

final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();

setChildFrame(child, childLeft + getLocationOffset(child), childTop,childWidth, childHeight);

2.DebugTextView

  setChildFrame最终调用的是子元素的layout方法,子View是DebugTextView,所以调用到的是DebugTextView的layout方法,即View的layout方法。具体再往下,调用就是DebugTextView的onLayout方法。

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mDeferScroll >= 0) {
            int curs = mDeferScroll;
            mDeferScroll = -1;
            bringPointIntoView(Math.min(curs, mText.length()));
        }
    }

三.总结

  onMeasure和onLayout结束之后,getMeasuredWidth方法和getWidth方法区别?

①获取的时机不一致 getMeasuredWidth方法是在measure过程结束后就可以获取到 getWidth方法是在layout过程结束后才可以获取到.

②计算的方式不一致 getMeasuredWidth方法获取的值是setMeasuredDimension方法已经设置好的 getWidth方法获取的值是通过视图右边坐标减去左边坐标得到的.

  measure过程决定了view的宽高,measure完成以后,可以通过getgetMeasuredWidth和xxx获得view测量后的宽和高,在几乎所有的情况下,它就等于view最终的宽高,但是特殊情况除外。Layout过程决定了view的四个顶点的坐标和实际的view的宽高,完成以后,可以通过getTop、getBottom、getLeft、getRight来拿到view的四个顶点的位置,并可以通过getWidth和getHeight来拿到view的最终宽高。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    写给自己
    ○ 种一棵树最好的时间是十年前,其次是现在

    ○ 坚持输出,坚持书写,才可以持续成长

    ○ 所有美好事物的成长都是缓慢的

    ○ 既往不恋,未来不迎,当下不杂

    ○ 业精于勤,荒于嬉,行成于思,毁于随

    ○将军赶路 不追小兔

    ○不要拘泥于语言,同样也不要拘泥于行业,眼光放远一点

    ○ 如果某件事你做的不够好,不必介怀,因为以后的每一次每一天你都会做得越来越好

    ○ 此心不于事上磨,更于何处磨此心

    ○ 保持热情,保持求知欲

    ○ 千里之行,始于足下

    ○ 最怕你一生碌碌无为,还安慰自己平凡可贵。

    ○ 对于任何事,要保持自觉积极主动探索尝试。但是如果自己不积极认真地生活,不管得到什么样的回答都没有用。——解忧杂货店
    个人资料
    • 访问:646904次
    • 积分:27
    • 等级:
    • 排名:千里之外
    • 原创:286篇
    • 转载:50篇
    • 译文:0篇
    • 评论:113条
    个人简介