View绘制的原因

导致View绘制的原因不外乎以下几种:

1、ViewRootImpl执行了addView()、updateViewLayout()、removeView等操作;
2、View本身的size尺寸大小或者position位置发生了变化;
3、View本身的可见性如 VISIBLE、INVISIBLE、GONE 等发生了变化;
4、View的内部状态发生了变化:press、enable、background、foreground等;

虽然performTraversals()、setView()、setLayoutParams()、performLayout()、invalidate()等方法也是发起View绘制的路径之一。比如View从0到1时的绘制,起点则是ActivityThread 的 handleResumeActivity()方法中的wm.addView(decor, l)方法:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    ......
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ......
    final Activity a = r.activity;
    .......
    // 要加载的Activity的Window为空 && 要加载的Activity没有被finished && 要加载的Activity即将可见。
    if (r.window == null && !a.mFinished && willBeVisible) {//向Window添加Activity的布局UI。
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        ......
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);//这里执行addView()操作,开始从0到1的View绘制;
            } else {
                ......
            }
        }
    // 如果窗口已经被添加,但是在恢复期间我们启动了另一个activity,那么还不要使窗口可见。
    } else if (!willBeVisible) {
        ......
    }
    ......
    // 如果窗口已经添加,那么它现在是可见的,我们不是简单地完成,也不是开始另一个activity。
    if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
        ......          
        WindowManager.LayoutParams l = r.window.getAttributes();
        if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) {
            l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit;
            if (r.activity.mVisibleFromClient) {
                ViewManager wm = a.getWindowManager();
                View decor = r.window.getDecorView();
                wm.updateViewLayout(decor, l);//这里执行updateViewLayout()操作,当View发生本质性的变化时,会执行View的更新操作;
            }
        }
        ......
        if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();// Activity可见
        }
    }
    ......
}

但这些方法调用和执行,最后都归集到了scheduleTraversals()方法。然后从这里开始没有异议的执行同一条路线去执行View的绘制工作。因而作为View绘制的起点,scheduleTraversals()方法有着举足轻重的作用。

scheduleTraversals()源码如下:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 核心重点代码就是这行,其中 Runnable对象 mTraversalRunnable是核心中的核心
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

是的,没看错,View绘制的秘密就在mTraversalRunnable中:

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

mTraversalRunnable对象的调用只有两个,一个是上面的scheduleTraversals()方法,另一个则是unscheduleTraversals()方法(执行着与scheduleTraversals()方法相反的逻辑)。

下面我们来看看TraversalRunnable接口内部的doTraversal()方法的实现:

原因树状图怎么绘制_原因分析图怎么做_

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // 核心实现,内部会执行 performMeasure()、performLayout()、performDraw()方法,依次展开View的测量、布局和绘制工作
        performTraversals();
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

performTraversals()方法源码伪代码如下:

private void performTraversals() {
    ......
    int relayoutResult = 0;
    boolean updatedConfiguration = false;
    final boolean isViewVisible = viewVisibility == View.VISIBLE;
    if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        ......
        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                 // 执行 Measure 操作
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ......
                boolean measureAgain = false;
                ......
                if (measureAgain) {
                    // 因为某些原因,再次执行 Measure 操作
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
                layoutRequested = true;
            }
        }
    } else {
        ......
    }
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    ......
    if (didLayout) {
        // 执行 Layout布局操作
        performLayout(lp, mWidth, mHeight);
        ......
    }
    ......
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    if (!cancelDraw && !newSurface) {
        ......
        // 执行 Draw 绘制操作
        performDraw();
    } else {
        if (isViewVisible) {
            // 因为某些原因,再次执行 scheduleTraversals() 操作
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            ......
        }
    }
}

performTraversals()方法主要干以下几件事情:

1、开启预测量:windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
2、开启窗口预布局:relayoutResult = relayoutWindow(params, viewVisibility, insetsPending)。relayoutWindow()方法是重新布局窗口的重要方法,它确保窗口中的所有视图都能按照最新的布局规则正确显示。
3、开启控件树测量:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
4、开启控件树布局:performLayout(lp, mWidth, mHeight);
5、判断各View是否需要重新绘制:if (!performDraw() && mActiveSurfaceSyncGroup != null) { mActiveSurfaceSyncGroup.markSyncReady(); }

基于上述几点,作为View绘制的起点,一般流程是:

scheduleTraversals()—>mTraversalRunnable—>doTraversal()—>performTraversals()—>performMeasure()(onMeasure())—>performLayout()(onLayout())—>performDraw()(onDraw())

performMeasure()

该方法会调用View的measure()方法开展View的measure()测量工作,但其实在View.measure()方法里面,则是调用onMeasure()方法具体展开测量工作;而ViewGroup则是没有自己的onMeasure()方法,但其子类重写了继承自View的onMeasure()方法。而且不同的子ViewGroup类,其onMeasure()方法实现也不同。但有一点是确定的,他们都会通过for循环递归遍历的调用子View的onMeasure()方法完成整个View树的子View的measure测量工作。

performLayout()

该方法会调用View的layout()方法内部会调用View的onLayout()方法来具体展开布局工作。但是View的onLayout()方法是一个空的实现,具体的Layout布局逻辑由其各个子View具体实现;而ViewGroup更狠,他的onLayout()方法直接是一个抽象方法,因此具体的Layout布局逻辑也只能由其各个子View去实现了。而在各个子viewGroup的onLayout()方法中,则是通过for循环递归遍历各个子View,并调用child.layout()方法完成各个子View的布局工作。

在布局流程上,具体是他先是调用父View的layout()方法来完成对本级View(他自己本身)的布局和定位。layout()方法接收4个参数:left、top、right、bottom,也就是一个View的四个顶点位置坐标,以此确定在父View中的位置。然后再通过for循环递归遍历View树上的各个子View,并调用子View的layout()方法完成各个子View的布局工作。

performDraw()

是调用我们根布局ViewGroup的onDraw()方法,该方法里面会依次调用backGround.Draw()方法绘制自己的背景,然后调用onDraw()方法绘制自身,接着调用dispatchDraw()方法绘制各个子View,最后是调用onDrawScrollBar()方法绘制装饰信息,至此View走完一个完整的绘制流程。

MeasureSpec

MeasureSpec,他是一个32位的整型数据,高2位表示SpecMod,低30位表示SpecSize,SpecMod有三种状态:一是AT_MOST,开发中对应wrap_content;二是EXACTLY,开发中对应match_parent或者具体的数值;三是UNSPECIFIED,对应的是无限制宽或者高的场景,比如RecycleView垂直滑动时,高度是不受限制的;在水平滑动时,长度是不受限制的。同样的,还有其他的View,如ScrollView/NestedScrollView的宽度/高度也是不受限制的。各级View中,DecorView的大小由其LayoutParams参数确定,其他的子View由自身的LayoutParams参数、自身的MeasureSpec参数和父View的LayoutParams参数共同确定。ViewGroup中子View的MeasureSpec参数是通过getChildMeasureSpec()方法来获取的。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。