一文完全搞懂Android View的繪製流程

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

上一篇中咱們講到了Android的觸摸事件傳遞機制,除此以外,關於Android View的繪製流程這一塊也是View相關的核心知識點。咱們都知道,PhoneWindow是Android系統中最基本的窗口系統,每一個Activity會建立一個。同時,PhoneWindow也是Activity和View系統交互的接口。DecorView本質上是一個FrameLayout,是Activity中全部View的祖先。java

1、開始:DecorView被加載到Window中

從Activity的startActivity開始,最終調用到ActivityThread的handleLaunchActivity方法來建立Activity,相關核心代碼以下:android

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ....
    // 建立Activity,會調用Activity的onCreate方法
    // 從而完成DecorView的建立
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.tolen, false, r.isForward, !r.activity..mFinished && !r.startsNotResumed);
    }
}

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;
    // 調用Activity的onResume方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.window == null &&& !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            // 獲得DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            // 獲得了WindowManager,WindowManager是一個接口
            // 而且繼承了接口ViewManager
            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) {
                a.mWindowAdded = true;
                // WindowManager的實現類是WindowManagerImpl,
                // 因此實際調用的是WindowManagerImpl的addView方法
                wm.addView(decor, l);
            }
        }
    }
}

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
}
複製代碼

在瞭解View繪製的總體流程以前,咱們必須先了解下ViewRoot和DecorView的概念。ViewRoot對應於ViewRootImpl類,它是鏈接WindowManager和DecorView的紐帶,View的三大流程均是經過ViewRoot來完成的。在ActivityThread中,當Activity對象被建立完畢後,會將DecorView添加到Window中,同時會建立ViewRootImpl對象,並將ViewRootImpl對象和DecorView創建關聯,相關源碼以下所示:git

// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ...
    ViewRootImpl root;
    View pannelParentView = null;
    synchronized (mLock) {
        ...
        // 建立ViewRootImpl實例
        root = new ViewRootImpl(view..getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    try {
        // 把DecorView加載到Window中
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        synchronized (mLock) {
            final int index = findViewLocked(view, false);
            if (index >= 0) {
                removeViewLocked(index, true);
            }
        }
        throw e;
    }
}
複製代碼

2、瞭解繪製的總體流程

繪製會從根視圖ViewRoot的performTraversals()方法開始,從上到下遍歷整個視圖樹,每一個View控件負責繪製本身,而ViewGroup還須要負責通知本身的子View進行繪製操做。performTraversals()的核心代碼以下。github

private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //執行測量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    //執行佈局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    //執行繪製流程
    performDraw();
}
複製代碼

performTraversals的大體工做流程圖以下所示:json

image

顯示不出來的可點擊這裏查看canvas

注意:微信

  • preformLayout和performDraw的傳遞流程和performMeasure是相似的,惟一不一樣的是,performDraw的傳遞過程是在draw方法中經過dispatchDraw來實現的,不過這並無本質區別。app

  • 獲取content:ide

    ViewGroup content = (ViewGroup)findViewById(android.R.id.content);oop

  • 獲取設置的View:

    content.getChildAt(0);

3、理解MeasureSpec

1.MeasureSpec源碼解析

MeasureSpec表示的是一個32位的整形值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize。MeasureSpec是View類的一個靜態內部類,用來講明應該如何測量這個View。MeasureSpec的核心代碼以下。

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK = 0X3 << MODE_SHIFT;
    
    // 不指定測量模式, 父視圖沒有限制子視圖的大小,子視圖能夠是想要
    // 的任何尺寸,一般用於系統內部,應用開發中不多用到。
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
    // 精確測量模式,視圖寬高指定爲match_parent或具體數值時生效,
    // 表示父視圖已經決定了子視圖的精確大小,這種模式下View的測量
    // 值就是SpecSize的值。
    public static final int EXACTLY = 1 << MODE_SHIFT;
    
    // 最大值測量模式,當視圖的寬高指定爲wrap_content時生效,此時
    // 子視圖的尺寸能夠是不超過父視圖容許的最大尺寸的任何尺寸。
    public static final int AT_MOST = 2 << MODE_SHIFT;
    
    // 根據指定的大小和模式建立一個MeasureSpec
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    
    // 微調某個MeasureSpec的大小
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        if (mode == UNSPECIFIED) {
            // No need to adjust size for UNSPECIFIED mode.
            return make MeasureSpec(0, UNSPECIFIED);
        }
        int size = getSize(measureSpec) + delta;
        if (size < 0) {
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }
}
複製代碼

MeasureSpec經過將SpecMode和SpecSize打包成一個int值來避免過多的對象內存分配,爲了方便操做,其提供了打包和解包的方法,打包方法爲上述源碼中的makeMeasureSpec,解包方法源碼以下:

public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}
複製代碼
2.DecorView的MeasureSpec的建立過程:
//desiredWindowWidth和desiredWindowHeight是屏幕的尺寸
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

private static int getRootMeaureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATRCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}
複製代碼
3.子元素的MeasureSpec的建立過程
// ViewGroup的measureChildWithMargins方法
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
    // 子元素的MeasureSpec的建立與父容器的MeasureSpec和子元素自己
    // 的LayoutParams有關,此外還和View的margin及padding有關
    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);
}

public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    
    // padding是指父容器中已佔用的空間大小,所以子元素可用的
    // 大小爲父容器的尺寸減去padding
    int size = Math.max(0, specSize - padding);
    
    int resultSize = 0;
    int resultMode = 0;
    
    switch (sepcMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimesion == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        
        // Parent has imposed a maximum size on us 
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
            
        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him 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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size....
                // find out how big it should be
                resultSize = 0;
                resultMode == MeasureSpec.UNSPECIFIED;
            }
            break;
        }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
複製代碼

普通View的MeasureSpec的建立規則以下:

image

注意:UNSPECIFIED模式主要用於系統內部屢次Measure的情形,通常不需關注。

結論:對於DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同決定;對於普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定。

4、View繪製流程之Measure

1.Measure的基本流程

由前面的分析可知,頁面的測量流程是從performMeasure方法開始的,相關的核心代碼流程以下。

private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    ...
    // 具體的測量操做分發給ViewGroup
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

// 在ViewGroup中的measureChildren()方法中遍歷測量ViewGroup中全部的View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        // 當View的可見性處於GONE狀態時,不對其進行測量
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

// 測量某個指定的View
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    
    // 根據父容器的MeasureSpec和子View的LayoutParams等信息計算
    // 子View的MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

// View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    // ViewGroup沒有定義測量的具體過程,由於ViewGroup是一個
    // 抽象類,其測量過程的onMeasure方法須要各個子類去實現
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ...
}

// 不一樣的ViewGroup子類有不一樣的佈局特性,這致使它們的測量細節各不相同,若是須要自定義測量過程,則子類能夠重寫這個方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // setMeasureDimension方法用於設置View的測量寬高
    setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// 若是View沒有重寫onMeasure方法,則會默認調用getDefaultSize來得到View的寬高
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 = sepcSize;
            break;
    }
    return result;
}
複製代碼
2.對getSuggestMinimumWidth的分析
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
複製代碼

若是View沒有設置背景,那麼返回android:minWidth這個屬性所指定的值,這個值能夠爲0;若是View設置了背景,則返回android:minWidth和背景的最小寬度這二者中的最大值。

3.自定義View時手動處理wrap_content時的情形

直接繼承View的控件須要重寫onMeasure方法並設置wrap_content時的自身大小,不然在佈局中使用wrap_content就至關於使用match_parent。解決方式以下:

protected void onMeasure(int widthMeasureSpec, 
int height MeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    // 在wrap_content的狀況下指定內部寬/高(mWidth和mHeight)
    int heightSpecSize = MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasureDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasureDimension(widthSpecSize, mHeight);
    }
}
複製代碼
4.LinearLayout的onMeasure方法實現解析
protected void onMeasure(int widthMeasureSpec, int hegithMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

// measureVertical核心源碼
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
    final View child = getVirtualChildAt(i);
    ...
    // Determine how big this child would like to be. If this or 
    // previous children have given a weight, then we allow it to 
    // use all available space (and we will shrink things later 
    // if need)
    measureChildBeforeLayout(
            child, i, widthMeasureSpec, 0, heightMeasureSpec,
            totalWeight == 0 ? mTotalLength : 0);
            
    if (oldHeight != Integer.MIN_VALUE) {
        lp.height = oldHeight;
    }
    
    final int childHeight = child.getMeasuredHeight();
    final int totalLength = mTotalLength;
    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + 
    lp.bottomMargin + getNextLocationOffset(child));
}
複製代碼

系統會遍歷子元素並對每一個子元素執行measureChildBeforeLayout方法,這個方法內部會調用子元素的measure方法,這樣各個子元素就開始依次進入measure過程,而且系統會經過mTotalLength這個變量來存儲LinearLayout在豎直方向的初步高度。每測量一個子元素,mTotalLength就會增長,增長的部分主要包括了子元素的高度以及子元素在豎直方向上的margin等。

// LinearLayout測量本身大小的核心源碼
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
...
setMeasuredDimension(resolveSizeAndSize(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    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:
            // 高度不能超過父容器的剩餘空間
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
複製代碼
5.在Activity中獲取某個View的寬高

因爲View的measure過程和Activity的生命週期方法不是同步執行的,若是View尚未測量完畢,那麼得到的寬/高就是0。因此在onCreate、onStart、onResume中均沒法正確獲得某個View的寬高信息。解決方式以下:

  • Activity/View#onWindowFocusChanged

    // 此時View已經初始化完畢 // 當Activity的窗口獲得焦點和失去焦點時均會被調用一次 // 若是頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地調用 public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasureWidth(); int height = view.getMeasuredHeight(); } }

  • view.post(runnable)

    // 經過post能夠將一個runnable投遞到消息隊列的尾部,// 而後等待Looper調用次runnable的時候,View也已經初 // 始化好了 protected void onStart() { super.onStart(); view.post(new Runnable() {

    @Override
          public void run() {
              int width = view.getMeasuredWidth();
              int height = view.getMeasuredHeight();
          }
      });
    複製代碼

    }

  • ViewTreeObserver

    // 當View樹的狀態發生改變或者View樹內部的View的可見// 性發生改變時,onGlobalLayout方法將被回調 protected void onStart() { super.onStart();

    ViewTreeObserver observer = view.getViewTreeObserver();
      observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
          
          @SuppressWarnings("deprecation")
          @Override
          public void onGlobalLayout() {
              view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
              int width = view.getMeasuredWidth();
              int height = view.getMeasuredHeight();
          }
      });
    複製代碼

    }

  • View.measure(int widthMeasureSpec, int heightMeasureSpec)

5、View的繪製流程之Layout

1.Layout的基本流程
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

// View.java
public void layout(int l, int t, int r, int b) {
    ...
    // 經過setFrame方法來設定View的四個頂點的位置,即View在父容器中的位置
    boolean changed = isLayoutModeOptical(mParent) ? 
    set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
    ...
    onLayout(changed, l, t, r, b);
    ...
}

// 空方法,子類若是是ViewGroup類型,則重寫這個方法,實現ViewGroup
// 中全部View控件佈局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    
}
複製代碼
2.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,)
    }
}

// layoutVertical核心源碼
void layoutVertical(int left, int top, int right, int bottom) {
    ...
    final int count = getVirtualChildCount();
    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.getMeasureWidth();
            final int childHeight = child.getMeasuredHeight();
            
            final LinearLayout.LayoutParams lp = 
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            ...
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
            
            childTop += lp.topMargin;
            // 爲子元素肯定對應的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
            // childTop會逐漸增大,意味着後面的子元素會被
            // 放置在靠下的位置
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            
            i += getChildrenSkipCount(child,i)
        }
    }
}

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

注意:在View的默認實現中,View的測量寬/高和最終寬/高是相等的,只不過測量寬/高造成於View的measure過程,而最終寬/高造成於View的layout過程,即二者的賦值時機不一樣,測量寬/高的賦值時機稍微早一些。在一些特殊的狀況下則二者不相等:

  • 重寫View的layout方法,使最終寬度老是比測量寬/高大100px

    public void layout(int l, int t, int r, int b) { super.layout(l, t, r + 100, b + 100); }

  • View須要多長measure才能肯定本身的測量寬/高,在前幾回測量的過程當中,其得出的測量寬/高有可能和最終寬/高不一致,但最終來講,測量寬/高仍是和最終寬/高相同

6、View的繪製流程之Draw

1.Draw的基本流程
private void performDraw() {
    ...
    draw(fullRefrawNeeded);
    ...
}

private void draw(boolean fullRedrawNeeded) {
    ...
    if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset, 
    scalingRequired, dirty)) {
        return;
    }
    ...
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, 
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
    ...
    mView.draw(canvas);
    ...
}

// 繪製基本上能夠分爲六個步驟
public void draw(Canvas canvas) {
    ...
    // 步驟一:繪製View的背景
    drawBackground(canvas);
    
    ...
    // 步驟二:若是須要的話,保持canvas的圖層,爲fading作準備
    saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);
    
    ...
    // 步驟三:繪製View的內容
    onDraw(canvas);
    
    ...
    // 步驟四:繪製View的子View
    dispatchDraw(canvas);
    
    ...
    // 步驟五:若是須要的話,繪製View的fading邊緣並恢復圖層
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    
    ...
    // 步驟六:繪製View的裝飾(例如滾動條等等)
    onDrawForeground(canvas)
}
複製代碼
2.setWillNotDraw的做用
// 若是一個View不須要繪製任何內容,那麼設置這個標記位爲true之後,
// 系統會進行相應的優化。
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
複製代碼
  • 默認狀況下,View沒有啓用這個優化標記位,可是ViewGroup會默認啓用這個優化標記位。
  • 當咱們的自定義控件繼承於ViewGroup而且自己不具有繪製功能時,就能夠開啓這個標記位從而便於系統進行後續的優化。
  • 當明確知道一個ViewGroup須要經過onDraw來繪製內容時,咱們須要顯示地關閉WILL_NOT_DRAW這個標記位。

7、總結

View的繪製流程和事件分發機制都是Android開發中的核心知識點,也是自定義View高手的內功心法。對於一名優秀的Android開發來講,主流三方源碼分析和Android核心源碼分析能夠說是必修課,下一篇,將會帶領你們更進一步深刻Android。

參考連接:

一、Android開發藝術探索

二、Android進階之光

三、Android高級進階

四、Android應用層View繪製流程與源碼分析

五、Android中View繪製流程淺析

讚揚

若是這個庫對您有很大幫助,您願意支持這個項目的進一步開發和這個項目的持續維護。你能夠掃描下面的二維碼,讓我喝一杯咖啡或啤酒。很是感謝您的捐贈。謝謝!


Contanct Me

● 微信:

歡迎關注個人微信:bcce5360

● 微信羣:

微信羣若是不能掃碼加入,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

相關文章
相關標籤/搜索