View繪制流程

涉及到的類:ViewRootImpl,PhoneWindow,ActivityThread,View,ViewGroup, DecorView
關(guān)系:view的attachInfo中包含ViewRootImpl,PhoneWindow中包含DecorView
創(chuàng)建時機:
入口:doTraversal方法,其由mChoreographer實例定期調(diào)用或者view自己設(shè)置重會
performTraversals 方法開始整理view繪制的相關(guān)流程,涉及到:
1.測量,具體的測量規(guī)則是什么,root的測量是怎么來的
2.為啥會有relayoutWindow,涉及到的window和activity的關(guān)系是什么
3.performLayout入口和相關(guān)規(guī)則
4.performDraw入口和相關(guān)規(guī)則,activity和surface的關(guān)系

root的默認寬高是屏幕寬高,默認模式為MATCH_PARENT 生成的MeasureSpec為EXACTLY模式的屏幕寬高 =》根據(jù)LayoutParam生成MeasureSpec
MeasureSpec 為 32位int值,高2位表示模式,其余30位表示具體的值
生成root的MeasureSpec值之后,開始向子View遞歸測量,分為兩種情況:
1.View:默認實現(xiàn)是調(diào)用onMeasure設(shè)置view的寬高(在背景寬高(backgroud)和最小寬高(minHeight||minWidth)中取大值,然后通過上一級的MeasureSpec和當前默認寬高生成當前view的MeasureSpec值,生成的規(guī)則是按照上一級的mode來的,如果上一級不限制大小(MeasureSpec.UNSPECIFIED),則為當前View的默認寬高,如果上一級限制大?。∕easureSpec.AT_MOST|| MeasureSpec.EXACTLY),則使用上一級的設(shè)置的寬高(這樣的話,當前view在layout文件中設(shè)置的wrap_content本質(zhì)上就和match_parent的效果一樣,所以自定義View時需要自己處理下view的測量工作)
2.ViewGroup:
生成子類的MeasureSpec值在getChildMeasureSpec(這個方法是系統(tǒng)默認實現(xiàn),用于自定義ViewGroup是的測量,系統(tǒng)不調(diào)用,而是重寫者自己調(diào)用的)方法中,生成邏輯是根據(jù)父View的MeasureSpec+子View的LayoutParam共同生成:MeasureSpec可以理解成 父View告訴子View自己當前的空間狀態(tài),請子View結(jié)合自己的要求(LayoutParam)來生成自己的空間狀態(tài)


MeasureSpec表.png

onMeasure方法是view空間大小狀態(tài)的入口方法,結(jié)束的標志就是其寬高被設(shè)置(setMeasuredDimension是默認設(shè)置寬高的方法)

performLayout方法是在測量后調(diào)用的,其通過調(diào)用rootView的layout開始將布局事件向View層分發(fā),layout方法的主要目的是根據(jù)父View的可用空間和位置信息,計算出每個子View的位置和可用空間,為onDraw方法做鋪墊,其結(jié)束的標志就是設(shè)置view的left top right bottom的坐標。
為了實現(xiàn)上述目的,框架層設(shè)定了layout和onLayout方法,View的layout方法不能被重寫,其主要負責調(diào)用自身的onLayout方法,其中View的onLayout方法是空實現(xiàn),ViewGroup的onLayout是抽象的,具體的onLayout是有具體的ViewGroup實現(xiàn)類實現(xiàn)的,比如LinearLayout,其onLayout就自身計算了子View的位置信息:
、、、
//該方法的四個參數(shù)都由父View傳遞,其描述的是當前view在父View的位置信息,同時也告訴子View,父View的空用空間信息
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;//當前LinearLayout的左邊留白

int childTop;
int childLeft;

// Where right end of child should go
final int width = right - left;//當前LiearLayout自身的寬度
int childRight = width - mPaddingRight;//子View的右邊界

// 算出當前LinearLayout可給子View使用的空間
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
switch (majorGravity) {
   …//省略其他計算childTop(子View頂部起始點)的方式
   default:
       childTop = mPaddingTop;//從這里可以看出,childTop是相對于其父ViewGroup的坐標,不是所有View共用一個坐標
       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();

        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;//加上子View自身的topMargin,可以看出 margin屬性的值是不計算在View的高度或者寬度內(nèi)的,而padding是計算在內(nèi)的
        setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                childWidth, childHeight);
        childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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

、、、
private void setChildFrame(View child, int left, int top, int width, int height) {
//將View在父布局中的可用位置告訴子View,如果子View自己沒有異議,就會設(shè)置自身的位置信息為在父View的可用位置
child.layout(left, top, left + width, top + height);
}
、、、

performDraw則負責分發(fā)draw事件,其會設(shè)置相關(guān)臟區(qū)域,實現(xiàn)局部重繪,ViewRootImpl類中draw方法會在硬件繪制和軟件繪制中選擇一個進行繪制,這里只關(guān)注下軟件繪制步驟:
1.繪制自身背景
2.繪制自身內(nèi)容(即調(diào)用自身onDraw方法)
3.繪制子View(是一種遞歸)
4.繪制ViewOverlay覆層
5.繪制裝飾,比如滾動條,actionbar等
6.繪制焦點視圖高亮

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容