从源码角度分析view的layout过程

上两篇文章主要讲述了view的measure过程,主要分析了xml文件中控件的height和width设置成不同值的时候,经过测量之后,如何计算出控件的真实高度。所以也就验证了我们经常所说的measure过程就是把match_parent等值转化成在具体设备上的具体的值。

本文主要分析一下layout的过程,同样我们以LinearLayout的layout过程为例。

在ViewRoot的performTraversals方法中首先是measure过程,然后接着是layout,layout开始也是从host.layout方法开始的。

host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);

host是一个viewGroup,ViewGroup继承自view,然而view中的layout是final的,所以这里调用的仍然是view的layout方法,然后调用了onLayout方法,所以我们从linearLayout的onLayout方法中开始分析

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

linearLayout的layout过程如下:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical();
        } else {
            layoutHorizontal();
        }
    }
    void layoutVertical() {
        final int paddingLeft = mPaddingLeft;
        int childTop = mPaddingTop;
        int childLeft;
        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;
        //根据LinearLayotu的gravity的值计算childTop的位置
        if (majorGravity != Gravity.TOP) {
           switch (majorGravity) {
               case Gravity.BOTTOM:
                   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();
                //拿到子View的LayoutParams
                final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                //计算子View在水平方向的childLeft
                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;
                // 调用child.layout方法设置child的布局位置
                setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                i += getChildrenSkipCount(child, i);
            }
        }
    }

首先根据mRight - mLeft获得width,根据width - paddingLeft - mPaddingRight获得childSpace,也就是实际上我们可以用的宽度。然后根据LinearLayotu的gravity的值计算childTop的位置:

  1. 为Gravity.BOTTOM:

    childTop = mBottom - mTop + mPaddingTop - mTotalLength;

    在这里当测量出来的mTotalLength总高度足够大的时候会出现childTop是负数的情况,也就是child的上面一部分会显示不全的情况,对应的图就像是下面的情况,图中上面的蓝色框内的一部分是不会显示的。

  2. 为Gravity.CENTER_VERTICAL:

    childTop += ((mBottom - mTop) - mTotalLength) / 2;

    对应的图片如下所示:

    接着循环遍历子view,如果子view为可见的,则计算出子view在水平方向上的childLeft,这里我们讨论的是垂直方向的布局,这里为什么会出现还要计算view在水平方向上的left呢,因为就算是垂直布局,每个子view他们也有marginleft和paddingLeft值。最后调用setChildFrame设置子view的布局位置,我们可以进入此方法看一下:

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

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

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            int drawn = mPrivateFlags & DRAWN;
            invalidate();
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mPrivateFlags |= HAS_BOUNDS;
            int newWidth = right - left;
            int newHeight = bottom - top;
            if (newWidth != oldWidth || newHeight != oldHeight) {
                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
            }
            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                mPrivateFlags |= DRAWN;
                invalidate();
            }
            // Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = true;
        }
        return changed;
    }

给子View布局最终调用的是setFrame方法,四个参数分别代表view在其父视图中的位置。首先判断如果这四个值和之前已经有的值是否相等,如果有一个不相等,就代表要重新布局,此时如果子view现在的高度和宽度和之前的宽高是不相同的,那么就必须要调用onSizeChanged方法,通知程序view的大小发生了变化,最后如果view是VISIBLE的,那么就要执行invalidate操作。

版权声明:本文为博主原创文章,未经博主允许不得转载(联系方式:QQ312037487 邮箱:[email protected])。

时间: 2024-10-09 17:34:32

从源码角度分析view的layout过程的相关文章

从源码角度分析linearLayout测量过程以及weight机制

???上文从源码角度分析了view和viewGroup的measure机制,如果还没有阅读的同志们,可以前往从源码角度分析Android View的绘制机制(一)阅读.下面我再结合linearLayout的measure过程解释以下两个问题的缘由. 问题一: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent

从源码角度分析ViewStub 疑问与原理

一.提出疑问 ViewStub比较简单,之前文章都提及到<Android 性能优化 三 布局优化ViewStub标签的使用>,但是在使用过程中有一个疑惑,到底是ViewStub上设置的参数有效还是在其包括的layout中设置参数有效?如果不明白描述的问题,可以看下以下布局伪代码. res/layout/main.xml <LinearLayout > <ViewStub android:id="@+id/viewstub" android:layout_w

从源码角度分析Android View的绘制机制(一)

在Android的学习道路上,每一个人员都免不了去翻阅Android的源码,因为只有从源码的角度分析问题,我们才能真正的玩转Android开发.最近由于工作比较闲,总想着想写点什么东西,正好自己也可以整理一下.考虑到view的显示机制是自定义view的基础,也是面试中经常被问到的问题,所以记录此文,和大家共享,因水平有限,望大家踊跃拍砖,不胜感激. 有过自定义view的同行们都应该知道,view的显示依托于activity的setContentView方法依附到PhoneWindow窗体上的,在

从源码角度分析Android中的Binder机制的前因后果

前面我也讲述过一篇文章<带你从零学习linux下的socket编程>,主要是从进程通信的角度开篇然后延伸到linux中的socket的开发.本篇文章依然是从进程通信的角度去分析下Android中的进程通信机制. 为什么在Android中使用binder通信机制? 众所周知linux中的进程通信有很多种方式,比如说管道.消息队列.socket机制等.socket我们再熟悉不过了,然而其作为一款通用的接口,通信开销大,数据传输效率低,主要用在跨网络间的进程间通信以及在本地的低速通信.消息队列和管道

【原创】源码角度分析Android的消息机制系列(五)——Looper的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. Looper在Android的消息机制中就是用来进行消息循环的.它会不停地循环,去MessageQueue中查看是否有新消息,如果有消息就立刻处理该消息,否则就一直等待. Looper中有一个属性: static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 这也就解释了,前面我们所说的我们可以通过ThreadLocal实现Looper

【原创】源码角度分析Android的消息机制系列(三)——ThreadLocal的工作原理

ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 先看Android源码(API24)中对ThreadLocal的定义: public class ThreadLocal<T> 即ThreadLoca是一个泛型类,再看对该类的注释: /** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread th

从源码角度分析native层消息机制与java层消息机制的关联

上文从源码分析Handler机制中从java层分析了消息机制,接下来本文从native层去分析Android中的消息机制. 在一个消息驱动的系统中,最重要的就是消息队列和消息获取和处理,从上一篇文章可以看出handler的消息机制主要是靠MessageQueue进行消息列队,靠Looper进行消息循环,Looper的loop方法中进行轮询消息的实际操作还是依靠MessageQueue的next方法来获取消息,也就是说在这个消息驱动机制中最重要的就是MessageQueue这个类了.在Androi

Android的Message Pool是什么——源码角度分析

原文地址: http://blog.csdn.net/xplee0576/article/details/46875555 Android中,我们在线程之间通信传递通常采用Android的消息机制,而这机制传递的正是Message. 通常,我们使用Message.obtain()和Handler.obtainMessage()从Message Pool中获取Message,避免直接构造Message. 那么Android会否因为Message Pool缓存的Message对象而造成OOM呢?对于

Android的Message Pool是个什么鬼——源码角度分析

Android中,我们在线程之间通信传递通常采用Android的消息机制,而这机制传递的正是Message. 通常,我们使用Message.obtain()和Handler.obtainMessage()从Message Pool中获取Message,避免直接构造Message. 那么Android会否因为Message Pool缓存的Message对象而造成OOM呢?对于这个问题,我可以明确的说APP不会因Message Pool而OOM.至于为什么,可以一步步往下看,心急的可以直接看最后一节