View的繪制源碼學(xué)習(xí)筆記

View

This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for /widgets/, which are used to create interactive UI components (buttons, text fields, etc.).

Overview

首先是View這個類的繼承層次:

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource

可以看到是一個普通類兄旬,實(shí)現(xiàn)了3個接口。

View.java文件代碼共有27000多行,乍一看很嚇人殃恒,但其中注釋也占了很大部分氛堕,仔細(xì)看來主要分成以下方面

  • 構(gòu)造函數(shù)

  • 繪制相關(guān)

  • 各種getter/setter

  • 事件處理相關(guān)

這篇首先看看構(gòu)造函數(shù)以及繪制相關(guān)的源碼

構(gòu)造函數(shù)

View這個類的構(gòu)造函數(shù)一共有四個,方法簽名分別是:

public View(Context context)

public View(Context context, @Nullable AttributeSet attrs)

public View(Context context, @Nullable  AttributeSet attrs,  int defStyleAttr)

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr,  int defStyleRes)

其中第2第3個方法的實(shí)現(xiàn)都只是調(diào)用了第4個方法舔庶,而第4個方法又會首先調(diào)用第1個方法猪狈,因此先看下第1個方法箱沦,即單參數(shù)的構(gòu)造方法實(shí)現(xiàn):

public View(Context context) {
        mContext = context;
        mResources = context != null ? context.getResources() : null;
        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
        // Set some flags defaults
        mPrivateFlags2 =
                (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
                (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
                (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
                (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
                (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
                (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
        mUserPaddingStart = UNDEFINED_PADDING;
        mUserPaddingEnd = UNDEFINED_PADDING;
        mRenderNode = RenderNode.create(getClass().getName(), this);

        if (!sCompatibilityDone && context != null) {
            final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

            // Older apps may need this compatibility hack for measurement.
            sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;

            // Older apps expect onMeasure() to always be called on a layout pass, regardless
            // of whether a layout was requested on that View.
            sIgnoreMeasureCache = targetSdkVersion < Build.VERSION_CODES.KITKAT;

            Canvas.sCompatibilityRestore = targetSdkVersion < Build.VERSION_CODES.M;
            Canvas.sCompatibilitySetBitmap = targetSdkVersion < Build.VERSION_CODES.O;
            Canvas.setCompatibilityVersion(targetSdkVersion);

            // In M and newer, our widgets can pass a “hint” value in the size
            // for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
            // know what the expected parent size is going to be, so e.g. list items can size
            // themselves at 1/3 the size of their container. It breaks older apps though,
            // specifically apps that use some popular open source libraries.
            sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;

            // Old versions of the platform would give different results from
            // LinearLayout measurement passes using EXACTLY and non-EXACTLY
            // modes, so we always need to run an additional EXACTLY pass.
            sAlwaysRemeasureExactly = targetSdkVersion <= Build.VERSION_CODES.M;

            // Prior to N, layout params could change without requiring a
            // subsequent call to setLayoutParams() and they would usually
            // work. Partial layout breaks this assumption.
            sLayoutParamsAlwaysChanged = targetSdkVersion <= Build.VERSION_CODES.M;

            // Prior to N, TextureView would silently ignore calls to setBackground/setForeground.
            // On N+, we throw, but that breaks compatibility with apps that use these methods.
            sTextureViewIgnoresDrawableSetters = targetSdkVersion <= Build.VERSION_CODES.M;

            // Prior to N, we would drop margins in LayoutParam conversions. The fix triggers bugs
            // in apps so we target check it to avoid breaking existing apps.
            sPreserveMarginParamsInLayoutParamConversion =
                    targetSdkVersion >= Build.VERSION_CODES.N;

            sCascadedDragDrop = targetSdkVersion < Build.VERSION_CODES.N;

            sHasFocusableExcludeAutoFocusable = targetSdkVersion < Build.VERSION_CODES.O;

            sAutoFocusableOffUIThreadWontNotifyParents = targetSdkVersion < Build.VERSION_CODES.O;

            sUseDefaultFocusHighlight = context.getResources().getBoolean(
                    com.android.internal.R.bool.config_useDefaultFocusHighlight);

            sThrowOnInvalidFloatProperties = targetSdkVersion >= Build.VERSION_CODES.P;

            sCanFocusZeroSized = targetSdkVersion < Build.VERSION_CODES.P;

            sAlwaysAssignFocus = targetSdkVersion < Build.VERSION_CODES.P;

            sAcceptZeroSizeDragShadow = targetSdkVersion < Build.VERSION_CODES.P;

            sCompatibilityDone = true;
        }
    }

概括來說,主要做了以下幾件事:

  • 對View可訪問的關(guān)鍵變量進(jìn)行賦值:mContext和mResources等

  • 根據(jù)系統(tǒng)版本對其成員變量進(jìn)行賦值雇庙,這里的變量大多數(shù)都是boolean型

然后是第4個4參數(shù)的構(gòu)造函數(shù)實(shí)現(xiàn):

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        this(context);

        final TypedArray a = context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

        if (mDebugViewAttributes) {
            saveAttributeData(attrs, a);
        }

        Drawable background = null;

        int leftPadding = -1;
        int topPadding = -1;
        int rightPadding = -1;
        int bottomPadding = -1;
        int startPadding = UNDEFINED_PADDING;
        int endPadding = UNDEFINED_PADDING;

        int padding = -1;
        int paddingHorizontal = -1;
        int paddingVertical = -1;

        int viewFlagValues = 0;
        int viewFlagMasks = 0;

        boolean setScrollContainer = false;

        int x = 0;
        int y = 0;

        float tx = 0;
        float ty = 0;
        float tz = 0;
        float elevation = 0;
        float rotation = 0;
        float rotationX = 0;
        float rotationY = 0;
        float sx = 1f;
        float sy = 1f;
        boolean transformSet = false;

        int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
        int overScrollMode = mOverScrollMode;
        boolean initializeScrollbars = false;
        boolean initializeScrollIndicators = false;

        boolean startPaddingDefined = false;
        boolean endPaddingDefined = false;
        boolean leftPaddingDefined = false;
        boolean rightPaddingDefined = false;

        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

        // Set default values.
        viewFlagValues |= FOCUSABLE_AUTO;
        viewFlagMasks |= FOCUSABLE_AUTO;

        final int N = a.getIndexCount();
        for (int I = 0; I < N; I++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.View_background:
                    background = a.getDrawable(attr);
                    break;
                case com.android.internal.R.styleable.View_padding:
                    padding = a.getDimensionPixelSize(attr, -1);
                    mUserPaddingLeftInitial = padding;
                    mUserPaddingRightInitial = padding;
                    leftPaddingDefined = true;
                    rightPaddingDefined = true;
                    break;
                case com.android.internal.R.styleable.View_paddingHorizontal:
                    paddingHorizontal = a.getDimensionPixelSize(attr, -1);
                    mUserPaddingLeftInitial = paddingHorizontal;
                    mUserPaddingRightInitial = paddingHorizontal;
                    leftPaddingDefined = true;
                    rightPaddingDefined = true;
                    break;
                // 此處省略約400行
            }
        }

        setOverScrollMode(overScrollMode);

        // Cache start/end user padding as we cannot fully resolve padding here (we don’t have yet
        // the resolved layout direction). Those cached values will be used later during padding
        // resolution.
        mUserPaddingStart = startPadding;
        mUserPaddingEnd = endPadding;

        if (background != null) {
            setBackground(background);
        }

        // setBackground above will record that padding is currently provided by the background.
        // If we have padding specified via xml, record that here instead and use it.
        mLeftPaddingDefined = leftPaddingDefined;
        mRightPaddingDefined = rightPaddingDefined;

        if (padding >= 0) {
            leftPadding = padding;
            topPadding = padding;
            rightPadding = padding;
            bottomPadding = padding;
            mUserPaddingLeftInitial = padding;
            mUserPaddingRightInitial = padding;
        } else {
            if (paddingHorizontal >= 0) {
                leftPadding = paddingHorizontal;
                rightPadding = paddingHorizontal;
                mUserPaddingLeftInitial = paddingHorizontal;
                mUserPaddingRightInitial = paddingHorizontal;
            }
            if (paddingVertical >= 0) {
                topPadding = paddingVertical;
                bottomPadding = paddingVertical;
            }
        }

        if (isRtlCompatibilityMode()) {
            if (!mLeftPaddingDefined && startPaddingDefined) {
                leftPadding = startPadding;
            }
            mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
            if (!mRightPaddingDefined && endPaddingDefined) {
                rightPadding = endPadding;
            }
            mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial;
        } else {
            final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined;

            if (mLeftPaddingDefined && !hasRelativePadding) {
                mUserPaddingLeftInitial = leftPadding;
            }
            if (mRightPaddingDefined && !hasRelativePadding) {
                mUserPaddingRightInitial = rightPadding;
            }
        }

        internalSetPadding(
                mUserPaddingLeftInitial,
                topPadding >= 0 ? topPadding : mPaddingTop,
                mUserPaddingRightInitial,
                bottomPadding >= 0 ? bottomPadding : mPaddingBottom);

        if (viewFlagMasks != 0) {
            setFlags(viewFlagValues, viewFlagMasks);
        }

        if (initializeScrollbars) {
            initializeScrollbarsInternal(a);
        }

        if (initializeScrollIndicators) {
            initializeScrollIndicatorsInternal();
        }

        a.recycle();

        // Needs to be called after mViewFlags is set
        if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
            recomputePadding();
        }

        if (x != 0 || y != 0) {
            scrollTo(x, y);
        }

        if (transformSet) {
            setTranslationX(tx);
            setTranslationY(ty);
            setTranslationZ(tz);
            setElevation(elevation);
            setRotation(rotation);
            setRotationX(rotationX);
            setRotationY(rotationY);
            setScaleX(sx);
            setScaleY(sy);
        }

        if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
            setScrollContainer(true);
        }

        computeOpaqueFlags();
    }

概括來說谓形,主要做了兩件事情:

  • 從xml中將屬性分析出來,然后賦值到對應(yīng)的變量中疆前,函數(shù)的70%的代碼都在做這件事情

  • 對Padding寒跳、transform等屬性進(jìn)行賦值

繪制

一個View的繪制,需要經(jīng)歷3個階段:

  • 需要知道這個View所占的區(qū)域有多大

  • 需要知道這個View所處的位置是哪里

  • 需要知道這個View外觀長什么樣

以上3個階段分別對應(yīng)measure(),layout(),draw()函數(shù)

measure()
首先來看measure函數(shù)的源碼:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id" + getId() + ": "
                        + getClass().getName() + “#onMeasure() did not set the"
                       + " measured dimension by calling"
                       + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

閱讀一個函數(shù)竹椒,從讀懂函數(shù)簽名開始:從measure()的函數(shù)簽名中可以得到以下信息:

  • final函數(shù)童太,函數(shù)被關(guān)鍵字final標(biāo)記,意味著不可被子類重寫胸完,但仔細(xì)看函數(shù)體內(nèi)书释,大量的代碼都是與設(shè)置boolean標(biāo)志位相關(guān)的,并沒有進(jìn)行measure赊窥,真正measure的地方在上述38行的onMeasure()方法中征冷,這也是自定義View時進(jìn)行測量操作的地方

  • 入?yún)idthMeasureSpec和heightMeasureSpec,一個View需要確定自己有多大誓琼,不能為所欲為,需要知道兩點(diǎn)信息:/父容器對其大小的限制/和/父容器允許的大小肴捉,/入?yún)⒌膚idthMeasureSpec和heightMeasureSpec分別從寬和高兩個維度用1個變量給出了上述2點(diǎn)信息腹侣,下面具體看下是如何做到的

MeasureSpec
在View的onMeasure()方法中,會調(diào)用getDefaultSize(int size, int measureSpec)方法來獲得這個View默認(rèn)的size齿穗,看下這個方法的源碼即可了解MeasureSpec的原理

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;
    }

上述代碼中關(guān)鍵的兩行傲隶,第3行MeasureSpec.getMode()得到的就是父容器對其大小的限制,而MeasureSpec.getSize()得到的是父容器允許/建議的大小

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

getMode()和getSize()方法的實(shí)現(xiàn)非常簡潔窃页,這里的MODE_MASK取值為0X3<<30跺株,即32位,高2位為11脖卖,余下為0乒省,因?yàn)樯厦鎯蓚€方法的含義分別是:getMode為取measureSpec的高2位,getSize為取measureSpec的低30位畦木,通過這個方式袖扛,1個32位的變量即可存儲模式和尺寸兩個信息
在getDefaultSize()函數(shù)中也能看到,得到的specMode有3種取值:

  • UNSPECIFIED:父容器對該View的大小沒有限制

  • AL_MOST:最多不能超過給出的specSize

  • EXACTLTY:取值要為給出的specSize

meMeasure的最終目標(biāo),是在onMeasure()方法中蛆封,將測量得到的寬高唇礁,賦值給View的寬高,用于后續(xù)使用

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

layout()

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;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        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;
            }

            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 I 0; i I numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if (!wasLayoutValid && isFocused()) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            if (canTakeFocus()) {
                // We have a robust focus, so parents should no longer be wanting focus.
                clearParentsWantFocus();
            } else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
                // layout. In this case, there's’no guarantee that parent layouts will be evaluated
                // and thus the safest action is to clear focus here.
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                clearParentsWantFocus();
            } else if (!hasParentWantsFocus()) {
                // original requestFocus was likely on this view directly, so just clear focus
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
            }
            // otherwise, we let parents handle re-assigning focus during their layout passes.
        } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            View focused = findFocus();
            if (focused != null) {
                // Try to restore focus as close as possible to our starting focus.
                if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
                    // Give up and clear focus once we’v’ reached the top-most parent which wants
                    // focus.
                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                }
            }
        }

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

layout()函數(shù)惨篱,作用是確定View的坐標(biāo)盏筐,通過left/top/right/bottom四個值來確定,有2點(diǎn)值得記錄的:

  • 進(jìn)行l(wèi)ayout()時砸讳,會檢查是否已經(jīng)進(jìn)行了onMeasure()琢融,確保layout()在onMeasure(0之后

  • 和measure()與onMeasure()類似,真正進(jìn)行l(wèi)ayout操作的绣夺,在onLayout()方法里吏奸,也是自定義View確定布局的地方,在View.java里陶耍,onLayout()函數(shù)是一個空實(shí)現(xiàn)

draw()
確定了View的大小和位置之后奋蔚,就可以進(jìn)行實(shí)際的繪制操作了

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            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, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            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, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

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

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

            // we’re done…
            return;
        }

        ……
    }

draw()過程,源碼的注釋比較清晰烈钞,主要有7個步驟:

  • 繪制背景

  • 如果有需要泊碑,保存圖層信息,用于淡出準(zhǔn)備

  • 繪制內(nèi)容毯欣,這一步最關(guān)鍵馒过,也和measure()/layout()相似,這里會調(diào)用onDraw()來繪制真正的內(nèi)容

  • 繪制子view

  • 如果有需要酗钞,繪制類似陰影效果

  • 繪制View的裝飾腹忽,如滾動條

  • 繪制焦點(diǎn)效果

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砚作,隨后出現(xiàn)的幾起案子窘奏,更是在濱河造成了極大的恐慌,老刑警劉巖葫录,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件着裹,死亡現(xiàn)場離奇詭異,居然都是意外死亡米同,警方通過查閱死者的電腦和手機(jī)骇扇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來面粮,“玉大人少孝,你說我怎么就攤上這事“静裕” “怎么了韭山?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我钱磅,道長梦裂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任盖淡,我火速辦了婚禮年柠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘褪迟。我一直安慰自己冗恨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布味赃。 她就那樣靜靜地躺著掀抹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪心俗。 梳的紋絲不亂的頭發(fā)上傲武,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機(jī)與錄音城榛,去河邊找鬼揪利。 笑死,一個胖子當(dāng)著我的面吹牛狠持,可吹牛的內(nèi)容都是我干的疟位。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼喘垂,長吁一口氣:“原來是場噩夢啊……” “哼甜刻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起正勒,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤得院,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昭齐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矾柜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年阱驾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怪蔑。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡里覆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缆瓣,到底是詐尸還是另有隱情喧枷,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站隧甚,受9級特大地震影響车荔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜戚扳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一忧便、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帽借,春花似錦珠增、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脆荷,卻和暖如春凝垛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背简烘。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工苔严, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孤澎。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓届氢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親覆旭。 傳聞我的和親對象是個殘疾皇子退子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351