View的Layout过程源码分析

简介: /** * 文档描述: * View的Layout过程源码分析 * * 原创作者: * 谷哥的小弟 http://blog.csdn.
/**
	 * 文档描述:
	 * View的Layout过程源码分析
	 * 
	 * 原创作者:
	 * 谷哥的小弟 http://blog.csdn.net/lfdfhl
	 * 
	 * 分析笔记:
	 * View在经历过第一阶段的measure之后,进入到第二阶段layout.
	 * 该阶段的目的是
	 * (1)设置View自身的大小和位置.
	 * (2)设置View的子View大小和位置.
	 * 
     */
    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        /**
         * 第一步:
         * 利用setFrame()判断View的尺寸是否发生了变化.
         * setFrame()的分析请参见下面的源码.
         * 
         * 这一步就体现layout()的一个重要作用:
         * 设置View自身的大小和位置
         */
        boolean changed = setFrame(l, t, r, b);
        /**
         * 第二步:
         * View的尺寸发生了变化或者需要重新布局,进入该if代码段
         */
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
            }

            /**
             * 第三步:
             * 调用View的onLayou()方法.
             * 这是一个非常重要的地方.
             * 
             * 查看源码可以发现:
             * View和ViewGroup中默认的onLayout方法都是空方法.
             * 
             * 一般而言:
             * 继承自View的子类不用覆写该layout(),重点应该关注onDraw().
             * 继承自ViewGroup的子类不用覆写该layout(),重点应该关注onLayout()
             * 且在onLayout方法中依次循环子View,并调用子View的layout方法!!!
             * 
             * 举个例子:
             * 在ViewGroupSubClass的onLayout()方法中常见如下代码:
             * int childCount = getChildCount();
             * for (int i = 0; i < childCount; i++) {
             *      View childView = getChildAt(i);
             *      childView.layout(left, top, right, bottom); 
             *  }
             *  
             * 所以如果继承自ViewGroup在布局阶段应该重点关注onLayout方法中!!
             * 
             * 这一步就体现layout()的另外一个重要作用:
             * 设置View的子View大小和位置.
             * 
             */
            onLayout(changed, l, t, r, b);
            
            mPrivateFlags &= ~LAYOUT_REQUIRED;
            /**
             * 第四步:
             * 调用所有OnLayoutChangeListener接口的实现,通知View大小和位置发生了改变 .
             * 比如某个子view.addOnLayoutChangeListener(listener).这个listener就保存
             * 在了mOnLayoutChangeListeners中.
             * 这个listener在开发中还是挺有用的,值得关注
             */
            if (mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
        mPrivateFlags &= ~FORCE_LAYOUT;
    }
    
    
    
    
    
    
    
    
    
    /**
     * setFrame()方法源码分析
     * 该方法主要作用:
     * Assign a size and position to this view.
     * 为View指定大小和位置.
     * 
     * 在View中表示View的大小和位置常常用到四个变量:
     * int left, int top, int right, int bottom
     * 这四个变量可以分别用以下方法获得:
     * getLeft(),getTop(),getRight(),getBottom()
     * 
     * 但是请注意:
     * left   表示该View自身的左边距离parent的左边的距离
     * right  表示该View自身的右边距离parent的左边的距离
     * top    表示该View自身的上边距离parent的上边的距离
     * bottom 表示该View自身的下边距离parent的上边的距离
     * 
     * 只要知道了这四个值也就自然确定了View的大小和坐标
     * 其中宽高为:
     * int width = right - left;
     * int height = bottom - top;
     * 其中坐标为:
     * 左上角为(left,top),右下角为(right,bottom)
     * 
     *
     * 注意该方法的返回值:
     * return true if the new size and position are different than the previous ones
     * 如果该View的尺寸发生了变化则返回true,否则返回false.
     * 
     */
    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }
        /**
         * 将新旧left,right,top,bottom进行比较,若任意一对值不相等则将changed设置为true
         */
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            
            changed = true;

            int drawn = mPrivateFlags & PFLAG_DRAWN;

            /**
             * 第一步:
             * 计算View原先的宽和高
             */
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            /**
             * 第二步:
             * 计算View现在的宽和高
             */
            int newWidth = right - left;
            int newHeight = bottom - top;
            
            /**
             * 第三步:
             * 比较View的新旧尺寸.
             * 如果尺寸发生了变化则设置sizeChanged为true.
             * 该值稍后会用到
             */
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            /**
             * 第四步:
             * 刷新
             */
            invalidate(sizeChanged);

            /**
             * 第五步:
             * 将新得到的left,top,right,bottom保存到View的成员变量中
             */
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
            mPrivateFlags |= PFLAG_HAS_BOUNDS;


            /**
             * 第六步:
             * 如果View的尺寸发生了变化则执行sizeChange()
             */
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                invalidateParentCaches();
            }

            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = true;
            if (mForegroundInfo != null) {
                mForegroundInfo.mBoundsChanged = true;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
        return changed;
    }
    
    

相关文章
|
5月前
|
Android开发
Android面试题之View的invalidate方法和postInvalidate方法有什么区别
本文探讨了Android自定义View中`invalidate()`和`postInvalidate()`的区别。`invalidate()`在UI线程中刷新View,而`postInvalidate()`用于非UI线程,通过消息机制切换到UI线程执行`invalidate()`。源码分析显示,`postInvalidate()`最终调用`ViewRootImpl`的`dispatchInvalidateDelayed`,通过Handler发送消息到UI线程执行刷新。
63 1
|
5月前
|
消息中间件 前端开发 Android开发
Android面试题自定义View之Window、ViewRootImpl和View的三大流程
Android开发中,View的三大核心流程包括measure(测量)、layout(布局)和draw(绘制)。MeasureSpec类在测量过程中起到关键作用,它结合尺寸大小和模式(EXACTLY、AT_MOST、UNSPECIFIED)来指定View应如何测量。onMeasure方法用于自定义View的测量,布局阶段,ViewGroup调用onLayout确定子元素位置,而draw阶段按照特定顺序绘制背景、内容、子元素和装饰。整个流程始于ViewRootImpl的performTraversals,该方法触发测量、布局和绘制。
123 0
|
7月前
|
Android开发 容器
[Android]View的事件分发机制(源码解析)
[Android]View的事件分发机制(源码解析)
62 0
|
存储 缓存
RecyclerView 动画原理 | 换个姿势看源码(pre-layout)
RecyclerView 动画原理 | 换个姿势看源码(pre-layout)
81 0
|
存储 缓存 索引
RecyclerView 动画原理 | pre-layout,post-layout 与 scrap 缓存的关系
RecyclerView 动画原理 | pre-layout,post-layout 与 scrap 缓存的关系
89 0
|
API Android开发
Android View滑动相关的基础知识点
*本文涉及到的知识点:MotionEvent、ViewConfiguration、VelocityTracker 、GestureDetector、scrollTo、scrollBy、Scroller、OverScroller*
|
Android开发
图+源码,读懂View的Layout方法
本篇文章就带大家学习 View 绘制三大方法的第二个方法——Layout 方法。
图+源码,读懂View的Layout方法
|
Android开发 容器
View工作原理分析1 - 初识ViewRoot和 DecorView
以下相关资料均来自 Android艺术探索,部分内容加入了一些我个人的理解。
151 0
|
缓存 安全 Android开发
Android | 带你探究 LayoutInflater 布局解析原理
Android | 带你探究 LayoutInflater 布局解析原理
249 0
Android | 带你探究 LayoutInflater 布局解析原理