View的绘制流程

1、View的绘制流程的开始
Android中有太多太多的方法可以开启一个View的绘制流程,比如 view.setBackgroundColor() view.addView()等等。。

        LinearLayout linearLayout=new LinearLayout(this);
        linearLayout.setBackgroundColor(Color.parseColor("#ff0000"));// 可以开启View的绘制流程
        linearLayout.addView(topView);// 可以开启View的绘制流程

我们一步步来查看源码,发现他们最后都调用到了View的requestLayout()方法,下面我们来看一下这个方法

    public void requestLayout() {
       // ...
       mParent.requestLayout();
       // ...
    }

我们发现,View的requestLayout() 最终调用了mParent.requestLayout();方法,这里的mParent其实就是 ViewRootImpl 这个类,为什么是这个类呢? 我们来从activity的启动来分析一下

在ActivityThread类中

        private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // ...
        // 启动activity 调用activity的onCreat()
        Activity a = performLaunchActivity(r, customIntent);

        // 调用activity的onResume()
        handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    }

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        //  我们可以从源码一步步跟踪,发现这个 vm 就是WindowManagerImpl
        ViewManager wm = a.getWindowManager();
        // 我们这里就是调用WindowManagerImpl的addView()方法
        wm.addView(decor, l);
    }

然后我们来看WindowManagerImpl的addView()方法

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

继续来看 mGlobal.addView()方法(WindowManagerGlobal 类中)

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // ...
        ViewRootImpl root;
        View panelParentView = null;
        // ...
        // 在这个方法创建ViewRootImpl类
        root = new ViewRootImpl(view.getContext(), display);
        // 调用ViewRootImpl类的setView()方法
        root.setView(view, wparams, panelParentView);
    }

继续来到ViewRootImpl类的setView()方法

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
       // ...
       requestLayout();
       // ...
       // 我们来看最关键的这个方法,这里调用了View的assignParent()方法,并把ViewRootImpl类自己传进去
       view.assignParent(this);
       // ...      
    }

我们继续来看View的assignParent()方法

    void assignParent(ViewParent parent) {
        // 这里就是将前面我们要用的mParent 置为 ViewRootImpl
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }

到此我们分析了View中 mParent.requestLayout(); 其实是调用了 ViewRootImpl 的 requestLayout() 方法

下面我们着重分析 ViewRootImpl 的 requestLayout() 方法

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // ...
            scheduleTraversals();
        }
    }

    void scheduleTraversals() {
        // ...
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        // ...
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            // ...
            performTraversals();
            // ...
        }
    }

我们最终调用到performTraversals()这个方法,我们View的绘制流程从这里才刚刚开始

    private void performTraversals() {
        // ...
        // view的测量,用于指定和测量layout中所有控件的宽高
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        // ...
        // view的摆放
        performLayout(lp, mWidth, mHeight);
        // ...
        // view的绘制
        performDraw();
    }

1.1 我们先来看一下performMeasure方法

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        // ...
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        // ...
    }

    // 下面是View中的方法
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        // ...
        // 我们的第一个比较重要的方法出现了(测量view的宽高)
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        // ...        
    }

    // 这个时候我们就需要去看具体ViewGroup的onMeasure()方法,
    // 我们就用LinearLayout来做分析,其他的layout其实都一个套路,只是实现方式不一样而已
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

下面我们就来看一下LinearLayout中的onMeasure()方法是如何实现的

    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);
            // 测量子view的宽与高
            // 这个时候,子view的宽与高才有了值
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);
        }
        // 高度的size,有一套算法,每个item在竖方向叠加所得
        int heightSize = mTotalLength;
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        // 设置自己的宽与高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
    }

    void measureChildBeforeLayout(View child, int childIndex,
            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
            int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
                heightMeasureSpec, totalHeight);
    }

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 获取子类的mode与size
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        // 获取子类的mode与size
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        // 调用子类measure方法,进一步调用子类的onMeasure()方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


    // 结论: 子view的mode会根据父类的mode来共同决定
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 获取父类的宽高模式
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // 父布局是一个指定的值 MeasureSpec.EXACTLY
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                // 子布局是一个指定的值,则resultSize = childDimension  并且子类的模式也是 MeasureSpec.EXACTLY;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 子布局是一个MATCH_PARENT,则resultSize = size(这里的size为父类的size)  并且子类的模式也是 MeasureSpec.EXACTLY;
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子布局是一个WRAP_CONTENT,则resultSize = size(这里的size为父类的size)  并且子类的模式也是 MeasureSpec.AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父布局是一个自适应布局 MeasureSpec.AT_MOST
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // 子布局是一个指定的值,则resultSize = childDimension  并且子类的模式也是 MeasureSpec.EXACTLY;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 子布局是一个MATCH_PARENT,则resultSize = size(这里的size为父类的size)  并且子类的模式也是 MeasureSpec.AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子布局是一个WRAP_CONTENT,则resultSize = size(这里的size为父类的size)  并且子类的模式也是 MeasureSpec.AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

1.2我们再来看一下performLayout方法

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
       // ...
       // 调用View的layout方法
       host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        // ...
    }

    // 然后来到View 的layout方法
    public void layout(int l, int t, int r, int b) {
        // ...
        // 调用自己的onLayout方法
        onLayout(changed, l, t, r, b);
        // ...
    }

    // 我们发现这里是一个空实现,这就要到具体的ViewGroup实现类中查看具体实现,我们也是拿LinearLayout来分析
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

    // 下面我们来看LinearLayout的onLayout方法
    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);
        }
    }

    // 我们来看竖方向的layout方法
    void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;

        int childTop;
        int childLeft;
        
        // ... 循环获取子view,并摆放child View
        
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {// 如果子view不是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;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;

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

                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }

                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }

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

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

    // 设置子view的layout
    private void setChildFrame(View child, int left, int top, int width, int height) {        
        child.layout(left, top, left + width, top + height);
    }

1.3我们再来看一下performDraw方法

    private void performDraw() {
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;

        draw(fullRedrawNeeded);
    }

    private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
             return;
        } 
    }

     private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        final Canvas canvas;
        mView.draw(canvas);
        return true;
     }

    // 然后我们调到View的draw方法
    public void draw(Canvas canvas) {
        // 画背景
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // 有一个判断,如果是ViewGroup的话,dirtyOpaque = true,所以ViewGroup是默认不会调用onDraw方法 通过onDraw()绘制自身内容;
        if (!dirtyOpaque) onDraw(canvas);

        // 空方法dispatchDraw()
        dispatchDraw(canvas);

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    }

    // 现在我们来看一下LinearLayout中的dispatchDraw方法
    // 通过dispatchDraw()绘制子View;
    protected void dispatchDraw(Canvas canvas) {
        // 循环获取子view,并调用子view的onDraw方法
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
        }
    }

    // 调用此方法,就回调到子view的draw方法
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

总结一下:
1、总流程 : ViewRootImpl类中 requestLayout() -> scheduleTraversals() -> doTraversal() -> performTraversals() -> performMeasure() -> performLayout() -> performDraw()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容