View的繪制流程

View繪制前相關(guān)流程概述

  1. 在Activity被實(shí)例化后調(diào)用Activity的attach方法時(shí)會(huì)實(shí)例化PhoneWindow,并通過PhoneWindow的setWindowManager方法與WindowManager關(guān)聯(lián)。

  2. Activity的onCreate方法中會(huì)通過setContentView實(shí)例化DecorView,并將Activity中的布局文件添加到DecorView的content中。

  3. ActivityThread的handleResumeActivity中,會(huì)將DecorView添加到WindowManager中,即執(zhí)行 WMS添加Window的流程

  4. 在Window添加過程中會(huì)實(shí)例化ViewRootImpl,并且將DecorView傳遞給ViewRootImpl。

  5. ViewRootImpl是一個(gè)Android視圖層接口的頂部,是View和WindowManager的橋梁,ViewRootImpl與Choreographer協(xié)同完成View的繪制,也負(fù)責(zé)接收底層的觸摸事件的中轉(zhuǎn)分發(fā)。

View的繪制流程概述

  1. TraversalRunnable的run方法調(diào)用doTraversal方法開啟View的繪制流程

  2. doTraversal方法中首先會(huì)根據(jù)mTraversalScheduled的標(biāo)記判斷是否需要執(zhí)行繪制,接著移除同步屏障,調(diào)用performTraversals開始繪制。

  3. performTraversals中會(huì)根據(jù)Window的LayoutParams計(jì)算DecorView的MeasureSpec,然后分別執(zhí)行performMeasure、performLayout、performDraw。

  4. View的測量可分為View的測量和ViewGroup的測量,View的測量流程:

    • 調(diào)用View的measure方法執(zhí)行公用的測量,然后執(zhí)行onMeasure

    • onMeasure中會(huì)通過getDefaultSize確定并設(shè)置View自身的大小

    • getDefaultSize確定View的大小是根據(jù)父View傳來的MeasureSpec參數(shù)確定的

  5. ViewGroup除了測量本身大小,還會(huì)通過measureChildren遍歷子View,并通過measureChild測量子View的大?。?/p>

    • measureChild中拿到子View的LayoutParams并計(jì)算子View的MeasureSpec然后調(diào)用child.measure方法
    • ViewGroup是一個(gè)抽象類,沒有重寫onMeasure方法,具體的測量邏輯是在其子View中根據(jù)子View的特性進(jìn)行測量的
  6. 當(dāng)View的大小確定后,會(huì)調(diào)用performaLayout開始View的布局流程:

    • layout方法中通過setFrame來確定View的四個(gè)頂點(diǎn),即確定View在父View中的位置,并得到一個(gè)boolean值表示是否需要重新布局
    • 如果需要重新布局則調(diào)用onLayout開始布局,onLayout方法的作用是父View確定子View的位置。View和ViewGroup中都沒有onLayout的具體實(shí)現(xiàn)。需要子View根據(jù)自身特性進(jìn)行布局。
  7. 經(jīng)過測量和布局流程后會(huì)確定View的大小及位置,接著調(diào)用performDraw->DecorView.draw開始View的繪制過程。

    • drawBackground繪制背景
    • onDraw 繪制自己
    • dispatchDraw 繪制child
    • onDrawScrollBars 繪制裝飾

一 、View繪制流程前期準(zhǔn)備

1. Activity的初始化與Window的添加

handleLaunchActivity中會(huì)首先調(diào)用performLaunchActivity來創(chuàng)建一個(gè)Activity,并且執(zhí)行Activity的attach,接著通過Instrumentation的callActivityOnCreate方法調(diào)用了Activity的onCreate。代碼如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        Activity activity = null;
        // ...
        // 通過Instrumentation實(shí)例化Activity
        activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);

        try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        // 執(zhí)行Activity的attach方法
        activity.attach(appContext, this, getInstrumentation(), r.token,
        r.ident, app, r.intent, r.activityInfo, title, r.parent,
        r.embeddedID, r.lastNonConfigurationInstances, config,
        r.referrer, r.voiceInteractor, window, r.configCallback,
        r.assistToken);
        // ... 

        // 調(diào)用Activity的onCreate方法
        if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
        }
        }
        r.setState(ON_CREATE);
        }
        // ...

        return activity;
        }

(1)PhoneWindow關(guān)聯(lián)WindowManager

Activity在ActivityThread的performLaunchActivity中創(chuàng)建,創(chuàng)建完成后會(huì)首先執(zhí)行attach方法,在attach方法中實(shí)例化PhoneWindow并關(guān)聯(lián)WindowManager:

// Activity#attach():
   
final void attach(...) {
    attachBaseContext(context);
        //初始化 PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
        
        //初始化 WindowManager
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    mWindowManager = mWindow.getWindowManager();
}

Activity的attach方法中首先實(shí)例化了一個(gè)PhoneWindow,然后調(diào)用PhoneWindow的setWindowManager去初始化WindowManager,可以看下setWindowManager的代碼

// Window#setWindowManager():
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
        //...
        if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
        }

這里就是保證會(huì)創(chuàng)建一個(gè)WindowManagerImpl,并賦值給PhoneWindow的成員變量mWindowManager。WindowManagerImpl這個(gè)類在WMS中已經(jīng)多次見到。

(2)初始化DecorView

mInstrumentation.callActivityOnCreate 這行代碼會(huì)通過Instrumentation來調(diào)用Activity的onCreate方法。代碼如下:

// Instrumentation.java

public void callActivityOnCreate(Activity activity, Bundle icicle) {
        調(diào)用Activity的performCreate
        activity.performCreate(icicle);
        // ...
        }
// Activity.java 

final void performCreate(Bundle icicle) {
        performCreate(icicle, null);
        }

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        // ...

        if (persistentState != null) {
        // 調(diào)用onCreate
        onCreate(icicle, persistentState);
        } else {
        onCreate(icicle);
        }

        // ...
        }

可以看到,最終會(huì)調(diào)用Activity的onCreate方法,我們在onCreeate方法中會(huì)通過setContentView初始化DecorView,并將Activity的布局文件添加到DecorView的content布局中。在AppCompactActivity中還為DecorView設(shè)置了主題等布局。

(3)ViewRootImpl關(guān)聯(lián)DecorView

在handleResumeActivity中,通過WindowManager的實(shí)現(xiàn)類WindowManagerImpl的addView方法將DecorView添加到了Window,這里其實(shí)就是一個(gè)Window的添加的過程了。

// ActivityThread
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {

// ... 省略無關(guān)代碼

final Activity a = r.activity;
        if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        if (!a.mWindowAdded) {
        a.mWindowAdded = true;
        // 此處調(diào)用WindowManagerImpl的addView將DecorView添加到了WindowManagerImpl中
        wm.addView(decor, l);
        } else {
        a.onWindowAttributesChanged(l);
        }
        }
        // ... 省略無關(guān)代碼
        }

2. ViewRootImpl與Choreographer

ViewRootImpl 與 WMS、SurfaceFlinger 建立起連接后,此時(shí) View 還沒顯示出來,所有的 UI 最終都要通過 Surface 來顯示,那么 Surface 是什么時(shí)候創(chuàng)建的呢?

這就要回到前面所說的 ViewRootImpl 的 requestLayout 方法了,首先會(huì) checkThread 檢查是否是主線程,然后調(diào)用 scheduleTraversals 方法,scheduleTraversals 方法會(huì)先設(shè)置同步屏障,然后通過 Choreographer 類在下一幀到來時(shí)去執(zhí)行 doTraversal 方法。簡單來說,Choreographer 內(nèi)部會(huì)接受來自 SurfaceFlinger 發(fā)出的 Vsync 垂直同步信號(hào),這個(gè)信號(hào)周期一般是 16ms 左右。doTraversal 方法首先會(huì)先移除同步屏障,然后 performTraversals 真正進(jìn)行 View 的繪制流程,即調(diào)用 performMeasure、performLayout、performDraw。不過在它們之前,會(huì)先調(diào)用 relayoutWindow 通過 WindowSession 與 WMS 進(jìn)行交互,即把 Java 層創(chuàng)建的 Surface 與 Native 層的 Surface 關(guān)聯(lián)起來。

在ViewRootImpl的setView中會(huì)先執(zhí)行一次requestLayout,這次requestLayout的執(zhí)行時(shí)機(jī)是向AMS成功添加窗口后,收到Input事件之前執(zhí)行的,因?yàn)橹挥邢韧瓿蓽y量布局繪制流程后,各種觸摸事件才有一有。

同時(shí),我們也可以自行調(diào)用View的requestLayout來發(fā)起View的測量、布局和繪制流程??聪聄equestLayout的代碼:

// ViewRootImpl.java

@Override
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
        // 檢查是否是在主線程中,如果不是主線程則直接拋出異常
        checkThread();
        // mLayoutRequested標(biāo)記設(shè)置為true,在同一個(gè)Vsync周期內(nèi),執(zhí)行多次requestLayout的流程
        mLayoutRequested = true;
        scheduleTraversals();
        }
        }

scheduleTraversals方法中會(huì)發(fā)出一個(gè)同步屏障消息,并且將這次requestLayout請(qǐng)求放到TraversalRunnable的run方法中。然后通過Choreographer將TraversalRunnable發(fā)送出去,代碼如下:

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
@UnsupportedAppUsage
    void scheduleTraversals() {
            if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 通過Handler發(fā)送同步屏障阻塞同步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 通過Choreographer發(fā)出一個(gè)mTraversalRunnable,會(huì)在這里執(zhí)行
            mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            // ...
            }
            }

Choreographer會(huì)通過FrameDisplayEventReceiver監(jiān)聽Vsync信號(hào),等到Vsync到來時(shí)變化回調(diào)FrameDisplayEventReceiver的onVsync方法。并最終執(zhí)行Choreographer中的doFrame方法,這個(gè)方法里邊會(huì)去統(tǒng)計(jì)幀繪制的時(shí)間、真繪制信息、以及回調(diào)Callback,在ScheduleCallback中最終執(zhí)行了TraversalRunnable的run方法。

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

   void doTraversal() {
      if (mTraversalScheduled) {
         mTraversalScheduled = false;
         // 移除同步屏障
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

         if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
         }
         //  通過該方法開啟View的繪制流程,會(huì)調(diào)用performMeasure方法、performLayout方法和performDraw方法。
         performTraversals();

         if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
         }
      }
   }

TraversalRunnable的run方法執(zhí)行了doTraversal。doTraversal方法中會(huì)首先將同步屏障移除,然后調(diào)用performTraversals方法開啟View的繪制流程。

二、View繪制流程

performTraversals方法中會(huì)依次執(zhí)行performMeasure、performLayout和performDraw方法來開始View的測量、布局和繪制流程。

// ViewRootImpl
private void performTraversals() {
        // 根據(jù)Window的寬高與DecorView的LayoutParams來計(jì)算DecorView的MeasureSpec
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

        // ...

        //measur過程
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

        // ...

        //layout過程
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        // ...

        //draw過程
        performDraw();
        }

1. MeasureSpec

MeasureSpec是一個(gè)32位的int值,高位代表測量模式,低30位代表測量大小,MeasureSpec將SpecMode和SpecSize封裝成一個(gè)int值避免了過多的內(nèi)存分配。

MeasureSpec類中主要是對(duì)MeasureSpec的int值進(jìn)行打包和拆包的邏輯。核心代碼如下:

// View#MeasureSpec

public static class MeasureSpec {
   private static final int MODE_SHIFT = 30;
   private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

   @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
   @Retention(RetentionPolicy.SOURCE)
   public @interface MeasureSpecMode {}

   public static final int UNSPECIFIED = 0 << MODE_SHIFT;

   public static final int EXACTLY     = 1 << MODE_SHIFT;

   public static final int AT_MOST     = 2 << MODE_SHIFT;

   // 將size與mode打包到一個(gè)int中
   public static int makeMeasureSpec( int size, int mode) {
      if (sUseBrokenMakeMeasureSpec) {
         return size + mode;
      } else {
         return (size & ~MODE_MASK) | (mode & MODE_MASK);
      }
   }

   /**
    * 從MeasureSpec的int值中解析出mode
    */
   @MeasureSpecMode
   public static int getMode(int measureSpec) {
      //noinspection ResourceType
      return (measureSpec & MODE_MASK);
   }
   /**
    * 從MeasureSpec的int值中解析出size
    */
   public static int getSize(int measureSpec) {
      return (measureSpec & ~MODE_MASK);
   }

}

其中測量模式表示控件對(duì)應(yīng)的寬高模式:

  • UNSPECIFIED:父容器不對(duì)View做任何限制,要多大就多大,這種情況一般用于系統(tǒng)內(nèi)部,表示一種測量狀態(tài)。
  • EXACTLY:父容器已經(jīng)檢測出View所需要的 精確大小,這個(gè)時(shí)候View的最終大小就是SpecSize所指定的值。對(duì)應(yīng)LayoutParams中的MATCH_PARENT和具體的數(shù)值兩種模式。
  • AT_MOST:父容器制定了一個(gè)可用大小的SpecSize,View的大小不能大于這個(gè)值。具體是什么值還要看不同的View的具體表現(xiàn)。對(duì)應(yīng)LayoutParams中的WRAP_CONTENT。

performTraversals方法中首先通過getRootMeasureSpec根據(jù)Window的寬高與DecorView的LayoutParams來計(jì)算得到DecorView的MeasureSpec。代碼如下;

// ViewRootImpl
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        // MATCH_PARENT對(duì)應(yīng)EXACTLY模式    
        case ViewGroup.LayoutParams.MATCH_PARENT:
        // 將windowSize值與MeasureSpec.EXACTLY打包成一個(gè)int值MeasureSpec
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
        // WRAP_CONTENT對(duì)應(yīng)AT_MOST模式    
        case ViewGroup.LayoutParams.WRAP_CONTENT:
        // 將windowSize值與MeasureSpec.AT_MOST打包成一個(gè)int值MeasureSpec
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
default:
        // 將DecorView的具體寬高值與MeasureSpec.EXACTLY打包成一個(gè)int值MeasureSpec
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
        }
        return measureSpec;
        }
  • LayoutParams.MATCH_PARENT:精確模式,大小就是窗口的大小

  • LayoutParams.WRAP_CONTENT:最大模式,大小不確定,但是不能超過窗口的大小。

  • 固定大?。ɡ?00dp):精確模式,大小為LayoutParams中指定的大小。

對(duì)于普通的View來說,它的MeasureSpec是由父View的MeasureSpec和自身的LayoutParmas決定的。ViewGroup中通過getChildMeasureSpec來計(jì)算子View的Measure,

// ViewGroup.java

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 計(jì)算子View Width的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
        mPaddingLeft + mPaddingRight, lp.width);
// 計(jì)算子View Height的MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
        mPaddingTop + mPaddingBottom, lp.height);
        // 調(diào)用子View的measure開始子View的測量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }

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) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY: // 父View是EXACTLY模式
        if (childDimension >= 0) { // 子View的LayoutParams寬/高設(shè)置了精確值
        // 子View的大小就是該子View的LayoutParams中設(shè)置的值
        resultSize = childDimension;
        // 子View的模式設(shè)置為EXACTLY
        resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // 子View的LayoutParams的寬/高設(shè)置了MATCH_PARENT
        // 子View的size設(shè)置為父View的size
        resultSize = size;
        // 子View的模式設(shè)置為EXACTLY
        resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // 子View的LayoutParams的寬/高設(shè)置了WRAP_CONTENT
        // 子View的size設(shè)置為父View的size
        resultSize = size;
        // 子View的模式設(shè)置為AT_MOST
        resultMode = MeasureSpec.AT_MOST;
        }
        break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST: // 父View是AT_MOST模式
        if (childDimension >= 0) {
        // 子View的大小就是該子View的LayoutParams中設(shè)置的值
        resultSize = childDimension;
        // 子View的模式設(shè)置為EXACTLY
        resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // 子View的size設(shè)置為父View的size
        resultSize = size;
        // 子View的模式設(shè)置為AT_MOST
        resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // 子View的size設(shè)置為父View的size
        resultSize = size;
        // 子View的模式設(shè)置為AT_MOST
        resultMode = MeasureSpec.AT_MOST;
        }
        break;
        // 不討論這種情況
        case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
        // Child wants a specific size... let them have it
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // Child wants to be our size... find out how big it should
        // be
        resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
        resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // Child wants to determine its own size.... find out how
        // big it should be
        resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
        resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }

對(duì)于上面的代碼可以歸納為如下表格:

2. View的測量流程

(1)View的測量

在ViewRootImpl中首先根據(jù)Window的寬高與DecorView的LayoutParamters來生成MeasureSpec,然后執(zhí)行performMeasure流程。performMeasure方法會(huì)去調(diào)用DecorView的measure方法,mesaure是一個(gè)位于View中的final修飾的方法,開發(fā)者無法重寫measure,在measure會(huì)進(jìn)行一些公用的測量,然后會(huì)調(diào)用onMeasure,并將MeasureSpec傳遞給onMeasure.

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
      if (mView == null) {
          return;
      }
      try {
          mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      } finally {
          Trace.traceEnd(Trace.TRACE_TAG_VIEW);
      }
}

通過mView.measure將繪制流程交給了DecorView,即執(zhí)行View中的measure方法。measure方法中會(huì)進(jìn)行一些公用的邏輯處理,接著就會(huì)調(diào)用onMeasure方法

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        onMeasure(widthMeasureSpec, heightMeasureSpec);  
}

而onMeasure的邏輯也非常簡單,僅僅是設(shè)置了View的默認(rèn)大小,代碼如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

getSuggestedMinimumWidth/Height會(huì)查看是否給View設(shè)置了背景和最小寬高,然后取最大值作為建議寬高,getSuggestedMinimumWidth代碼如下:

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

getDefaultSize中只有會(huì)根據(jù)measureSpec拿到測量模式和測量寬高,可以看到在AT_MOST和EXACTLY時(shí)都返回了MeasureSpec的specSize。即默認(rèn)情況下要么是固定值要么是match_parent.

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

由上述可以看出對(duì)于View的測量流程而言,其本身寬高直接受限于父View的 布局要求,舉例來說,父View被限制寬度為40px,子View的最大寬度同樣也需受限于這個(gè)數(shù)值。因此,在測量子View之時(shí),子View必須已知父View的布局要求,這個(gè) 布局要求, Android中通過使用 MeasureSpec 類來進(jìn)行描述。

(2)ViewGroup的測量

對(duì)于ViewGroup除了完成自身的測量外,還需要調(diào)用所有子元素的measure,各個(gè)子元素再遞歸執(zhí)行測量。ViewGroup是一個(gè)抽象類,它沒有重寫onMeasure方法。也就是說ViewGroup中并沒有實(shí)質(zhì)的測量流程,具體的實(shí)現(xiàn)是在其子類中,因?yàn)樽宇愋枰鶕?jù)自己的特性來進(jìn)行測量。

下面以LinearLayout的豎直布局為例:

// LinearLayout.java

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

measureVertical方法中的一部分代碼:

// LinearLayout.java

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // ...
    // 遍歷所有子View
    for (int i = 0; i < count; ++i) {
          final View child = getVirtualChildAt(i);
          // 先測量子View
          measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                  heightMeasureSpec, usedHeight);
          // 完成子View的測量后便可以拿到子View的高度
          final int childHeight = child.getMeasuredHeight();
          // ...

          final int totalLength = mTotalLength;
          // 計(jì)算所有子View的高度之和
          mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                 lp.bottomMargin + getNextLocationOffset(child));

          if (useLargestChild) {
              largestChildHeight = Math.max(childHeight, largestChildHeight);
          }
    }
  
    // 測量所有子View的總大小再加上上下padding,確定自身的高度。
    mTotalLength += mPaddingTop + mPaddingBottom; 
    // 將測量出的高度賦值給heightSize
    int heightSize = mTotalLength;
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    heightSize = heightSizeAndState & MEASURED_SIZE_MASK; 
  
    // ...
    
    // 測量自View完成后設(shè)置自身大小
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
}  

可以看到在LinearLayout測量豎直布局時(shí)會(huì)首先遍歷所有的子View,然后調(diào)用measureChildBeforeLayout對(duì)子View進(jìn)行測量。并將測量子View的高度累加存儲(chǔ)在mTotalLength中。測量完畢后將測量最終高度加上上下padding作為測量值并最終通過setMeasuredDimension方法來設(shè)置自身高度。

measureChildBeforeLayout方法中最終會(huì)調(diào)用child的measure方法來進(jìn)行子View的測量,代碼如下:

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

// ViewGroup
protected void measureChildWithMargins(View child,
    int parentWidthMeasureSpec, int widthUsed,
    int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
        mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
            + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
        mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
            + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

子View可能是ViewGroup也可能是一個(gè)View,measure中會(huì)調(diào)用onMeausre,如果子View是ViewGroup,那么則又會(huì)通過其自身的特性遍歷自身所有子View并最終確定自身寬高,如果是View,那么直接通過onMeasure就能確定自身的大小。

最后,LinearLayout最終高度跟LinearLayout自身設(shè)置的的高度參數(shù)也有關(guān)系,即可能是match_parent/wrap_content或者是固定值。所以,在最終通過resolveSizeAndState方法來最終確定自身的高度:

// View
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

總結(jié)一下,View的測量流程是一個(gè)自上而下發(fā)起的測量過程,ViewGroup會(huì)遍歷所有子View,并計(jì)算子View的大小,然后根據(jù)子View的大小自下而上最終確定出自身的大小。其實(shí)是一個(gè)典型的遞歸流程。

3. View的布局流程

完成View的測量流程后,在performTraversals方法中接著會(huì)調(diào)用performLayout方法,開始View的布局流程,簡化代碼如下:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {

    final View host = mView;
    // ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

performLayout中調(diào)用了DecorView的layout方法,layout方法同樣位于View中,ViewGroup沒有重寫這個(gè)方法。

(1)View中的布局

View的layout簡化后代碼如下:

// View.java

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    // 先將之前的位置信息進(jìn)行保存
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    // 通過setFrame確定自身位置,返回值表示本次布局流程是否引發(fā)了布局的改變;
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    // 如果布局改變了,并且設(shè)置了PFLAG_LAYOUT_REQUIRED的標(biāo)記,則會(huì)調(diào)用onLayout布局子View
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);

        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
        // 刪除PFLAG_LAYOUT_REQUIRED標(biāo)記位
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.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);
            }
        }
    }

    final boolean wasLayoutValid = isLayoutValid();
    // 刪除PFLAG_LAYOUT_REQUIRED標(biāo)記位
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    // 添加已經(jīng)Layout的標(biāo)記,留給后續(xù)draw流程判斷
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    // ...
}

layout中首先會(huì)通過setFrame方法來設(shè)定View四個(gè)頂點(diǎn)的位置,View四個(gè)頂點(diǎn)的位置一旦確定那么View在父容器中的位置也就確定了。setFrame會(huì)返回一個(gè)boolean值,表示布局是否發(fā)生了改變。接下來的代碼會(huì)根據(jù)是否改變或者設(shè)置了PFLAG_LAYOUT_REQUIRED標(biāo)記來決定是否要布局子View。

PFLAG_LAYOUT_REQUIRED這個(gè)標(biāo)記是在調(diào)用View的requestLayout的時(shí)候添加上去的。requestLayout通過Parent一直調(diào)用到ViewRootImpl的requestLayout,等到Vsync到來的時(shí)候就會(huì)發(fā)起View的繪制流程,執(zhí)行測量、布局、和繪制的過程。這個(gè)時(shí)候,只有判斷有PFLAG_LAYOUT_REQUIRED標(biāo)記才會(huì)真正走這個(gè)繪制流程。

綜上,layout方法會(huì)首先通過setFrame確定自身位置,然后根據(jù)自身位置是否改變以及PFLAG_LAYOUT_REQUIRED標(biāo)記來決定是否調(diào)用onLayout重新布局子View。這里的View可能是一個(gè)View,也可能是一個(gè)ViewGroup。

  • 如果是View由于View的onLayout是一個(gè)空方法,那么確定自身位置后布局流程便結(jié)束了。
  • 如果是一個(gè)ViewGroup,那么在通過setFrame確定自身位置后,還會(huì)通過onLayout來布局子View,onLayout方法會(huì)確定所有子元素的位置。

(2)ViewGroup中的布局

在ViewGroup確定自身位置后,如果自身位置發(fā)生了改變,或者有PFLAG_LAYOUT_REQUIRED的標(biāo)記,那么就會(huì)調(diào)用onLayout方法,onLayout方法中會(huì)遍歷所有子View并確定子View的位置。

onLayout的具體實(shí)現(xiàn)和具體的布局有關(guān),所以ViewGroup中并沒有實(shí)現(xiàn)onLayout方法。具體的實(shí)現(xiàn)則是根據(jù)ViewGroup的自身特性來實(shí)現(xiàn)不同的布局,以LinearLayout為例:

// LinearLayout.java

@Override
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);
    }
}

仍然以豎直布局為例,來看layoutVertical方法

void layoutVertical(int left, int top, int right, int bottom) {
    
    // ...
    // 獲取所有子View的個(gè)數(shù)
    final int count = getVirtualChildCount();
    // 遍歷所有子View
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) { // 排除GONE的情況
            // 獲取子View的寬高
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
            // 獲取子View的LayoutParams參數(shù)
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            // ...
            // 如果給LinearLayout設(shè)置了Divider,則需要加上Divider的高度
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
            
            childTop += lp.topMargin;
            // 設(shè)置子View的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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

這個(gè)方法會(huì)遍歷所有子元素并調(diào)用setChildFrame方法來為子元素指定對(duì)應(yīng)的位置,其中childTop會(huì)逐步增大,意味著后邊的子元素會(huì)被放在靠下的位置。這剛好是豎直方向LinearLayout的特性。

至于setChildFrame僅僅是調(diào)用了子元素的layout方法而已,這樣父元素在layout方法中完成自己的定位后,就通過onLayout方法調(diào)用子元素的layout方法,子元素又會(huì)通過自己的layout方法來確定自己的位置,這樣一層一層的傳遞下去就完成了整個(gè)View樹的layout過程。setFrameLayout代碼如下:

private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

總的來看,layout的過程也是一個(gè)子上而下的過程,父View通過setFrame先確定自身的位置,然后遍歷所有子View,并執(zhí)行子View的布局流程,子View中亦是先確定自身的位置,然后逐個(gè)執(zhí)行所有子View,直到整個(gè)View樹布局完成。

4. View的繪制流程

draw的過程是將View繪制到屏幕上,View的繪制過程遵循如下幾步:

  • 繪制背景 drawBackground(canvas)
  • 繪制自己(onDraw)
  • 繪制子View(dispatchDraw)
  • 繪制裝飾(onDrawScrollBars)
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_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)
     */

    int saveCount;
    // Step 1, 繪制背景
    drawBackground(canvas);

    // 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, 調(diào)用onDraw,繪制自身
        onDraw(canvas);

        // Step 4, 調(diào)用dispatchDraw繪制子View
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, 繪制裝飾
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

   // ...
}

View繪制過程的傳遞是通過dispatchDraw來實(shí)現(xiàn)的,dispatchDraw會(huì)通過遍歷所有子元素的draw方法,如此draw事件就一層一層的傳遞下去,直到整個(gè)View樹都完成了繪制。

三、View繪制流程常見問題

1. View的getMeasureWidht與getWidth有什么區(qū)別?

View中g(shù)etWidth與getHeight的代碼如下:

public final int getWidth() {
        return mRight - mLeft;
        }

public final int getHeight() {
        return mBottom - mTop;
        }

從getWidht和getHeightd 源碼再結(jié)合mLeft、mRight、mTop和mBottom這四個(gè)遍歷值的賦值來看,getWidth的返回值剛好就是View的測量值。因此,在View的默認(rèn)實(shí)現(xiàn)匯總View的測量寬高和最終寬高是相等的,只不過測量寬高形成以Measure過程,而最終寬高形成與View的layout過程,即兩者的賦值實(shí)際不同。測量寬高的賦值實(shí)際稍微早了一些。因此,在日常的開發(fā)中,我們可以認(rèn)為View的測量寬高就等于最終寬高。

2. requestLayout()、invalidate()與postInvalidate()有什么區(qū)別?

requestLayout():該方法會(huì)遞歸調(diào)用父窗口的requestLayout()方法,直到觸發(fā)ViewRootImpl的performTraversals()方法,此時(shí)mLayoutRequestede為true,會(huì)觸發(fā)onMesaure()與onLayout()方法,不一定會(huì)觸發(fā)onDraw()方法。

invalidate():該方法遞歸調(diào)用父View的invalidateChildInParent()方法,直到調(diào)用ViewRootImpl的invalidateChildInParent()方法,最終觸發(fā)ViewRootImpl的performTraversals()方法,此時(shí)mLayoutRequestede為false,不會(huì)觸發(fā)onMesaure()與onLayout()方法,當(dāng)時(shí)會(huì)觸發(fā)onDraw()方法。

postInvalidate():該方法功能和invalidate()一樣,只是它可以在非UI線程中調(diào)用。一般說來需要重新布局就調(diào)用requestLayout()方法,需要重新繪制就調(diào)用invalidate()方法。

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

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

  • 簡介我們知道,在 Android 中,View 繪制主要包含 3 大流程: measure(測量):主要用于確定 ...
    Bfmall閱讀 3,139評(píng)論 0 18
  • 簡介 我們知道,在 Android 中,View 繪制主要包含 3 大流程: measure(測量):主要用于確定...
    Whyn閱讀 5,433評(píng)論 1 14
  • 一、需要了解的知識(shí) DecorViewDecroView 其實(shí)是一個(gè) FrameLayout,它包含了一個(gè)垂直的 ...
    Marker_Sky閱讀 2,889評(píng)論 0 3
  • 1 Android視圖層次結(jié)構(gòu) 上圖是針對(duì)比較老的Android系統(tǒng)版本中制作的,新的版本中會(huì)略有出入,但整體上沒...
    9283856ddec1閱讀 354評(píng)論 0 0
  • View的繪制和事件處理是兩個(gè)重要的主題,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳...
    Kelin閱讀 121,440評(píng)論 100 846

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