zoukankan      html  css  js  c++  java
  • 戏说Android view 工作流程《下》

    遍历View树performTraversals()执行过程

    view树遍历概述

    还是回到ViewRoot.java,我们直接看performTraversals(),该函数就是android系统View树遍历工作的核心。一眼看去,发现这个函数挺长的,但是逻辑是非常清晰的,其执行过程可简单概括为根据之前所有设置好的状态,判断是否需要计算视图大小(measure)、是否需要重新安置视图的位置(layout),以及是否需要重绘(draw)视图,可以用以下图来表示该流程。

      

      private void performTraversals() {
            // cache mView since it is used so much below...
    //1 处理mAttachInfo的初始化,并根据resize、visibility改变的情况,给相应的变量赋值。
            final View host = mView;
            final View.AttachInfo attachInfo = mAttachInfo;
            final int viewVisibility = getHostVisibility();
            boolean viewVisibilityChanged = mViewVisibility != viewVisibility
                    || mNewSurfaceNeeded;
            float appScale = mAttachInfo.mApplicationScale;
            WindowManager.LayoutParams params = null;
            if (mWindowAttributesChanged) {
                mWindowAttributesChanged = false;
                surfaceChanged = true;
                params = lp;
            }
            Rect frame = mWinFrame;
            if (mFirst) {
    
                // For the very first time, tell the view hierarchy that it
                // is attached to the window.  Note that at this point the surface
                // object is not initialized to its backing store, but soon it
                // will be (assuming the window is visible).
                attachInfo.mSurface = mSurface;
                attachInfo.mUse32BitDrawingCache = PixelFormat.formatHasAlpha(lp.format) ||
                        lp.format == PixelFormat.RGBX_8888;
                attachInfo.mHasWindowFocus = false;
                attachInfo.mWindowVisibility = viewVisibility;
                ......
            } 
    //2 如果mLayoutRequested判断为true,那么说明需要重新layout,不过在此之前那必须重新measure。
            if (mLayoutRequested) {
                // Execute enqueued actions on every layout in case a view that was detached
                // enqueued an action after being detached
                getRunQueue().executeActions(attachInfo.mHandler);
    
                if (mFirst) {
                    ......
                } 
            }
    //3 判断是否有子视图的属性发生变化,ViewRoot需要获取这些变化。
            if (attachInfo.mRecomputeGlobalAttributes) {
                ......
            }
    
            if (mFirst || attachInfo.mViewVisibilityChanged) {
                ......
            }
    
        
    //4 根据上面得到的变量数值,确定我们的view需要多大尺寸才能装下。于是就得measure了,有viewgroup的weight属性还得再做些处理
                     // Ask host how big it wants to be
                    host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                    mLayoutRequested = true;
                }
            }
    //5 measure完毕,接下来可以layout了。
            final boolean didLayout = mLayoutRequested;
            boolean triggerGlobalLayoutListener = didLayout
                    || attachInfo.mRecomputeGlobalAttributes;
            if (didLayout) {
                host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
    
            }
    
    //6 如果mFirst为true,那么会进行view获取焦点的动作。
            if (mFirst) {
                mRealFocusedView = mView.findFocus();
            }
    
            boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
    //7 终于,来到最后一步,前面的工作可以说都是铺垫,都是为了draw而准备的。
            if (!cancelDraw && !newSurface) {
                mFullRedrawNeeded = false;
                draw(fullRedrawNeeded);
    }
    

    下面我们就来会会view那三部曲吧。

    计算视图大小(measure)的过程

    整个view视图的Measure过程就是一个量体裁衣,按需分配的过程。看一下以下的递归过程:


    从上图可以看出,measure过程始于ViewRoot的host.measure(),调的就是view类的measure()函数,该函数然后回调onMeasure。如果host对象是一个ViewGroup实例,一般会重载onMeasure,如果没有的话,则会执行view类中默认的onMeasure。合理的情况是编程人员重载onMeasure并逐一对里面的子view进行measure。我们可以看一下view的measure方法:

       

      /**
         * 该方法在需要确定view所需尺寸大小时调用,父视图会提供宽和高的属性约束。
         * 具体视图完全可以在onMeasure中改变这些。
         * @see #onMeasure(int, int)
         */
        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
    
                // 首先清除dimension的设值
                mPrivateFlags &= ~MEASURED_DIMENSION_SET;
                // measure 自己, 并设置dimension
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                // 抛出未设值flag的异常
                if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                    throw new IllegalStateException("onMeasure() did not set the"
                            + " measured dimension by calling"
                            + " setMeasuredDimension()");
                }
                mPrivateFlags |= LAYOUT_REQUIRED;
            }
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
        }
    

    这里强烈建议去看看viewGroup实例FrameLayout和LinearLayout的onMeasure方法,一定会有所感悟的,尤其是LinerLayout的。这样对于viewGroup的专用标签pading和weight也会有新的体会。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="100dip"
        >
    <TextView  
        android:layout_width="fill_parent" 
        android:layout_height="20dip" 
        android:layout_weight="2"
        android:text="@string/hello"
        />
        <ListView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#ff00ff00"
        ></ListView>
    </LinearLayout>
    
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="100dip"
        >
    <TextView  
        android:layout_width="fill_parent" 
        android:layout_height="60dip" 
        android:layout_weight="2"
        android:text="@string/hello"
        />
        <ListView
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="2"
        android:background="#ff00ff00"
        ></ListView>
    </LinearLayout>
    

    请问以上两布局有无不同,能否自行画出?

    布局(layout)过程

    执行完measure过程,也就是说各个view的大小尺寸已经登记在案,现在它们要确定的是自己应该置身于何处,也就是摆放在哪里。好吧,这个就是layout的职责所在,让父视图按照子视图的大小及布局参数,将子视图放置在合适的位置上。

    同样需要看下以下流程图:


     public void layout(int l, int t, int r, int b) {
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
    //调用setFrame()函数给当前视图设置参数中指定的位置
            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()函数
                onLayout(changed, l, t, r, b);
                mPrivateFlags &= ~LAYOUT_REQUIRED;
    //4.0新增监听可捕获layout变化
                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);
                    }
                }
            }
    //layout完成,清楚标签
            mPrivateFlags &= ~FORCE_LAYOUT;
        }
    

           view中的该layout函数流程大概如下:

    1,调用setFrame()将位置参数保存,这些参数会保存到view内部变量(mLeft,mTop,mRight,mButtom)。如果有数值的变化那么会调用invalidate()重绘那些区域。

    2,回调onLayout(),View中定义的onLayout()函数默认什么都不做,View系统提供onLayout函数的目的是为了使系统包含有子视图的父视图能够在onLayout()函数对子视图进行位置分配,正因为这样,如果是viewGroup类型,就必须重载onLayout(),由此ViewGroup的onLayout为abstract也就很好解释了。

    3,清楚mPrivateFlags中的LAYOUT_REQUIRED标记,因为layout的操作已经完成了。

    为了对layout过程有更深的体会,有必要针对特定的一种viewGroup进行分析,我们还是把魔爪伸向linearLayout,看看它的onMeasure,onMeasure中会根据mOrientation变量,进行不同的layout,我们看一种就行了:

       

     void layoutVertical() {
            final int paddingLeft = mPaddingLeft;
            int childTop;
            int childLeft;       
            // 获得子视图可以用的宽度,顺便也把子视图左边沿的位置计算出来。
            final int width = mRight - mLeft;
            int childRight = width - mPaddingRight;     
            // Space available for child
            int childSpace = width - paddingLeft - mPaddingRight;
            
            final int count = getVirtualChildCount();
    //根据父视图中的gravity属性,决定子视图的起始位置。
            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
            final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
            switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already
                   childTop = mPaddingTop + mBottom - mTop - mTotalLength;
                   break;
                   // mTotalLength contains the padding already
               case Gravity.CENTER_VERTICAL:
                   childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
                   break;
               case Gravity.TOP:
               default:
                   childTop = mPaddingTop;
                   break;
            }
    //遍历所有子视图,为它们分配位置.setChildFrame()结果还是会调用child.layout()为子视图设置布局位置.
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
        ......
                    childTop += lp.topMargin;
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    

    绘制(draw)过程

    这是见证奇迹的时刻,draw过程就是要把view对象绘制到屏幕上,如果它是一个viewGroup,则需要递归绘制它所有的子视图。视图中有哪些元素是需要绘制的呢?

    1,view背景。所有view都会有一个背景,可以是一个颜色值,也可以是一张背景图片

    2,视图本身内容。比如TextView的文字

    3,渐变边框。就是一个shader对象,让视图看起来更具有层次感

    4,滚动条。

    按照惯例,还是先看一下绘制的总体流程吧:


    眼尖的同学应该发现了,上面这张图比前面的measure和layout多了一步:draw()。在performTraversals()函数中调用的是viewRoot的draw()函数,在该函数中进行一系列的前端处理后,再调用host.draw()。

    一般情况下,View对象不应该重载draw()函数,因此,host.draw()就是view.draw()。该函数内部过程也就是View系统绘制过程的核心过程,该函数中会依次绘制前面所说哦四种元素,其中绘制视图本身的具体实现就是回调onDraw()函数,应用程序一般也会重载onDraw()函数以绘制所设计的View的真正界面内容。

    Google源码的注释太nice了,我加任何说辞都显得多余,就不画蛇添足了:

    public void draw(Canvas canvas) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
            }
    
            final int privateFlags = mPrivateFlags;
            final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
            mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
    
            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
    
            // Step 1, draw the background, if needed
            int saveCount;
    
            if (!dirtyOpaque) {
                final Drawable background = mBGDrawable;
                if (background != null) {
                    final int scrollX = mScrollX;
                    final int scrollY = mScrollY;
    
                    if (mBackgroundSizeChanged) {
                        background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                        mBackgroundSizeChanged = false;
                    }
    
                    if ((scrollX | scrollY) == 0) {
                        background.draw(canvas);
                    } else {
                        canvas.translate(scrollX, scrollY);
                        background.draw(canvas);
                        canvas.translate(-scrollX, -scrollY);
                    }
                }
            }
    
            // skip step 2 & 5 if possible (common case)
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
            if (!verticalEdges && !horizontalEdges) {
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);
    
                // Step 4, draw the children
                dispatchDraw(canvas);
    
                // Step 6, draw decorations (scrollbars)
                onDrawScrollBars(canvas);
    
                // we're done...
                return;
            }
    
            /*
             * Here we do the full fledged routine...
             * (this is an uncommon case where speed matters less,
             * this is why we repeat some of the tests that have been
             * done above)
             */
    
            boolean drawTop = false;
            boolean drawBottom = false;
            boolean drawLeft = false;
            boolean drawRight = false;
    
            float topFadeStrength = 0.0f;
            float bottomFadeStrength = 0.0f;
            float leftFadeStrength = 0.0f;
            float rightFadeStrength = 0.0f;
    
            // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
    
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
    
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;        
            int length = (int) fadeHeight;
    
            // clip the fade length if top and bottom fades overlap
            // overlapping fades produce odd-looking artifacts
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }
    
            // also clip horizontal fades if necessary
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }
    
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
    
            saveCount = canvas.getSaveCount();
    
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
    
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }
    
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
    
            // Step 4, draw the children
            dispatchDraw(canvas);
    
            // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    
            if (drawTop) {
                matrix.setScale(1, fadeHeight * topFadeStrength);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                canvas.drawRect(left, top, right, top + length, p);
            }
    。。。。。
            canvas.restoreToCount(saveCount);
            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);
        }

    绘制完界面内容后,如果该视图还包含子视图,则调用dispatchDraw()函数,实际上起作用的还是viewGroup的dispatchDraw()函数。需要说明的是应用程序不应该再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。

     protected void dispatchDraw(Canvas canvas) {
            final int count = mChildrenCount;
            final View[] children = mChildren;
            int flags = mGroupFlags;
    //1 判断mGroupFlags是否设有FLAG_RUN_ANIMATION标识并且不为0.该layout动画指的是加载或移除子视图时候呈现的动画.
            if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
                final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
                final boolean buildCache = !isHardwareAccelerated();//硬件加速,4.0加入.
                for (int i = 0; i < count; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                        final LayoutParams params = child.getLayoutParams();
                        attachLayoutAnimationParameters(child, params, i, count);
                        bindLayoutAnimation(child);
                }
    
            }
    //2 处理padding属性,如果该viewGroup有设置.
            int saveCount = 0;
            final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
            if (clipToPadding) {
                saveCount = canvas.save();
                canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                        mScrollX + mRight - mLeft - mPaddingRight,
                        mScrollY + mBottom - mTop - mPaddingBottom);
    
            }
    //3 开始绘制子视图动画之前先清除flag.
            // We will draw our child's animation, let's reset the flag
            mPrivateFlags &= ~DRAW_ANIMATION;
            mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
    
            boolean more = false;
            final long drawingTime = getDrawingTime();
    //4 使用佛如循环,使viewGroup的子视图逐个调用drawChild函数.
            if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
                for (int i = 0; i < count; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                        more |= drawChild(canvas, child, drawingTime);
                    }
                }
            } else {
                for (int i = 0; i < count; i++) {
                    final View child = children[getChildDrawingOrder(count, i)];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                        more |= drawChild(canvas, child, drawingTime);
                    }
                }
            }
    
    //5 Draw any disappearing views that have animations
            if (mDisappearingChildren != null) {
                final ArrayList<View> disappearingChildren = mDisappearingChildren;
                final int disappearingCount = disappearingChildren.size() - 1;
                // Go backwards -- we may delete as animations finish
                for (int i = disappearingCount; i >= 0; i--) {
                    final View child = disappearingChildren.get(i);
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
    
            if (clipToPadding) {
                canvas.restoreToCount(saveCount);
            }
    
    // mGroupFlags might have been updated by drawChild()
            flags = mGroupFlags;
    
            if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
                invalidate(true);
            }
        }
    

    整个view的工作流程那是相当复杂的,这需要静下心来,加以时日细细揣摩才能略有体会,本人写有小Demo一个,就当抛砖引玉:ClickMe


      

  • 相关阅读:
    响应码异常HttpStatus not ok!statusCode:307
    SpringBoot的web项目使用JRebel启动错误
    SpringBoot启动遇到的找不到spring模块的怪事
    Redis(一)
    Redis一主二从Sentinel监控配置
    Redis命令
    IDEA
    sql server常用函数积累
    char,varchar和nvarchar有什么区别?
    SQL SERVER里的锁机制
  • 原文地址:https://www.cnblogs.com/aaa2832/p/3594766.html
Copyright © 2011-2022 走看看