当前位置: 首页 > article >正文

【Android】View工作原理

View 是Android在视觉上的呈现在界面上Android提供了一套GUI库,里面有很多控件,但是很多时候我们并不满足于系统提供的控件,因为这样就意味这应用界面的同类化比较严重。那么怎么才能做出与众不同的效果呢?答案是自定义View,也可以叫自定义控件,通过自定义View我们可以实现各种五花八门的效果。

除了 View 的三大流程以外, View 常见的回调方法也是需要熟练掌握的,比如构造方法、onAttach、onViVisibilityChanged、onDetach等。

ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。

在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。

View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程。measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。

在这里插入图片描述

performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分会别完成顶级View的measure、layout和draw,在performMeasure中会去调用measure方法,measure中又会去调用onMeasure方法,然后在onMeasure方法中会对所有的子元素进行measure过程,这样就完成了一次measure过程。然后子元素又会重复父容器的measure过程,最后完成整个View树的遍历。

另外两个的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的。

measure过程决定了View的宽/高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽/高;Layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可以通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高;Draw过程决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

理解MeasureSpec

MeasureSpec

MeasureSpec 是一个用于测量视图大小的概念,它通常与 ViewViewGroup 的测量过程密切相关。MeasureSpec 通过指定一个视图的宽度和高度模式以及实际大小,来控制视图的布局过程。

在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽/高。

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;
    public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << View.MeasureSpec.MODE_SHIFT) - 1) int size,
                                      @MeasureSpecMode int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    
    @UnsupportedAppUsage
    public static int makeSafeMeasureSpec(int size, int mode) {
        if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
            return 0;
        }
        return makeMeasureSpec(size, mode);
    }
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        int size = getSize(measureSpec);
        if (mode == UNSPECIFIED) {
            // No need to adjust size for UNSPECIFIED mode.
            return makeMeasureSpec(size, UNSPECIFIED);
        }
        size += delta;
        if (size < 0) {
            Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                    ") spec: " + toString(measureSpec) + " delta: " + delta);
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }
    public static String toString(int measureSpec) {
        int mode = getMode(measureSpec);
        int size = getSize(measureSpec);

        StringBuilder sb = new StringBuilder("MeasureSpec: ");

        if (mode == UNSPECIFIED)
            sb.append("UNSPECIFIED ");
        else if (mode == EXACTLY)
            sb.append("EXACTLY ");
        else if (mode == AT_MOST)
            sb.append("AT_MOST ");
        else
            sb.append(mode).append(" ");

        sb.append(size);
        return sb.toString();
    }
}

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包方法。

SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize。

SpecMode

SpecModeMeasureSpec 中的一个概念,用来描述测量时的约束类型。在 Android 中,MeasureSpec 被用来决定视图的宽度和高度,而 SpecMode 就是 MeasureSpec 的模式部分,它定义了视图的大小如何受到父视图的影响。

SpecMode 可以取以下三种值:

  1. MeasureSpec.EXACTLY
  • 说明:表示父视图已经为子视图指定了一个精确的尺寸,子视图必须严格遵循这个尺寸。

  • 使用场景:当父视图为子视图设定了明确的尺寸时,通常是通过 match_parent 或者指定固定尺寸来实现的。在这种模式下,子视图无法改变尺寸。

  • 举例:

    <TextView
        android:layout_width="200dp"
        android:layout_height="50dp"/>
    

    这里的宽度和高度会被视为 EXACTLY模式,子视图 TextView 必须使用指定的 200dp 和 50dp 尺寸。

  1. MeasureSpec.AT_MOST
  • 说明:表示父视图指定了一个最大尺寸,子视图可以根据自身内容来确定尺寸,但不能超过父视图给定的最大尺寸。

  • 使用场景:这种模式通常出现在 wrap_content 的情况下,父视图给定了一个最大值,而子视图的实际大小是根据内容决定的,但不会超过最大值。

  • 举例:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="50dp"/>
    

    这里TextView 的宽度是 wrap_content,因此它会根据内容来决定宽度,但高度被固定为 50dp。

  1. MeasureSpec.UNSPECIFIED
  • 说明:表示父视图没有为子视图的尺寸设置任何限制,子视图可以根据自己的需求来决定尺寸,通常不受任何约束。

  • 使用场景:这种模式通常用于 ListViewGridView 中的子项布局,它们的父视图允许子视图自适应尺寸。

  • 举例:

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    

    在这种情况下,LinearLayout的宽度和高度都是 wrap_content,父视图不会给 TextView 设置具体尺寸,TextView会根据内容自适应。

MeasureSpec和LayoutParams对应关系

系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,但是我们可以给View设置LayoutParams 。

在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。

LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。

对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。

在视图测量过程中,LayoutParams 主要影响 MeasureSpec 中的模式。例如:

  • 如果 LayoutParams 的宽度和高度被设置为 MATCH_PARENT,则对应的 MeasureSpec 模式通常会是 EXACTLY,并且 MeasureSpec 中的尺寸会是父容器的尺寸。
  • 如果 LayoutParams 的宽度和高度被设置为 WRAP_CONTENT,则对应的 MeasureSpec 模式通常会是 AT_MOST,并且 MeasureSpec 中的尺寸会是父容器可用的最大空间。
  • 如果 LayoutParams 中的宽高是某个固定值(例如 dp),则对应的 MeasureSpec 模式通常会是 EXACTLY,并且 MeasureSpec 中的尺寸会是该固定值。

LayoutParams 提供了视图布局的一些基本参数,而 MeasureSpec 是视图测量过程中实际计算和传递尺寸的工具。

  • LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小;
  • LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小;
  • 固定大小(比如100dp):精确模式,大小为LayoutParams中指定的大小。

对于普通View来说,View的measure过程由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);
    }

该方法会对子元素进行measure,在调用子元素的measure之前会先通过**getChildMeasureSpec()**得到子元素的MeasureSpec。

可以再看一下ViewGroup的**getChildMeasureSpec()**方法:

 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:
            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 (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 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 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);
    }

主要作用是根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec。

参数中的padding是指父容器中已占用的空间大小,因此子元素可用的大小为父容器的尺寸减去padding。

在这里插入图片描述

View的工作流程

View的工作流程主要是指measure、layout、draw这三大流程。

measure过程

View的measure过程

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

上面是View的onMeasure方法,setMeasuredDimension方法会设置View宽/高的测量值,因此我们只需要看getDefaultSize这个方法即可:

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

getDefaultSize返回的大小就是measureSpec中的specSize,而这个specSize就是View测量后的大小。

UNSPECIFIED这种情况,一般用于系统内部的测量过程。

这种情况下,View的大小为getDefaultSize的第一个参数size,即宽/高分别为getSuggestedMinimumWidth和getSuggestedMinimumHeight这两个方法的返回值:

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

从getSuggestedMinimumWidth的代码可以看出,如果View没有设置背景,那么View的宽度为mMinWidth。而 mMinWidth 对 应 于

android:minWidth这个属性所指定的值,因此View的宽度即为android:minWidth属性所指定的值。如果View指定了背景,则View的宽度为max。

我们看一下Drawable的getMinimumWidth方法:

public int getMinimumHeight() {
        final int intrinsicHeight = getIntrinsicHeight();
        return intrinsicHeight > 0 ? intrinsicHeight : 0;
    }

getMinimumWidth返回的就是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0。

ViewGroupmeasure过程

ViewGroup除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。

ViewGroup提供了一个叫measureChildren的方法:

 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];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

ViewGroup在measure时,会对每一个子元素进行measure。

里面的measureChild方法:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法来进行测量。

layout过程

Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。

public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        if (isTraversalTracingEnabled()) {
            Trace.beginSection(mTracingStrings.onMeasureBeforeLayout);
        }
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        if (isTraversalTracingEnabled()) {
            Trace.endSection();
        }
        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) {
        if (isTraversalTracingEnabled()) {
            Trace.beginSection(mTracingStrings.onLayout);
        }
        onLayout(changed, l, t, r, b);
        if (isTraversalTracingEnabled()) {
            Trace.endSection();
        }

        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 = 0; 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've 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);
    }

    notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
}

首先会通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop和mBottom这四个值,View的四个顶点一旦确定,那么View在父容器中的位置也就确定了 。

接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置,,和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关,所以View和ViewGroup均没有真正实现onLayout方法。

draw过程

Draw过程就比较简单了,它的作用是将View绘制到屏幕上面。

View的绘制过程遵循如下几步:

  1. 绘制背景background.draw(canvas)
  2. 绘制自己(onDraw)
  3. 绘制children(dispatchDraw)
  4. 绘制装饰(onDrawScrollBars)
 public void draw(@NonNull 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)
         *      7. If necessary, draw the default focus highlight
         */

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

        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
            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 (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

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

        /*
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = (int) fadeHeight;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength * fadeHeight > 1.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();
        int topSaveCount = -1;
        int bottomSaveCount = -1;
        int leftSaveCount = -1;
        int rightSaveCount = -1;

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            if (drawTop) {
                topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
            }

            if (drawBottom) {
                bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
            }

            if (drawLeft) {
                leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
            }

            if (drawRight) {
                rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // Step 3, draw the content
        onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        // must be restored in the reverse order that they were saved
        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            if (solidColor == 0) {
                canvas.restoreUnclippedLayer(rightSaveCount, p);

            } else {
                canvas.drawRect(right - length, top, right, bottom, p);
            }
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            if (solidColor == 0) {
                canvas.restoreUnclippedLayer(leftSaveCount, p);
            } else {
                canvas.drawRect(left, top, left + length, bottom, p);
            }
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            if (solidColor == 0) {
                canvas.restoreUnclippedLayer(bottomSaveCount, p);
            } else {
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
        }

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            if (solidColor == 0) {
                canvas.restoreUnclippedLayer(topSaveCount, p);
            } else {
                canvas.drawRect(left, top, right, top + length, p);
            }
        }

        canvas.restoreToCount(saveCount);

        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 (isShowingLayoutBounds()) {
            debugDrawFocus(canvas);
        }
    }

View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递了下去。

View有一个特殊的方法setWillNotDraw:

public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

如果一个View不需要绘制任何内容,那么设置这个标记位为true以后,系统会进行相应的优化。

默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。


已经到底啦!!


http://www.kler.cn/a/420650.html

相关文章:

  • 安装MySQL 5.7 亲测有效
  • ultralytics-YOLOv11的目标检测解析
  • 机器学习概述,特征工程简述2.1——2.3
  • [node.js] [HTTP/S] 实现 requests 发起 HTTP/S/1.1/2.0 请求
  • 公共github私有化教程
  • 【Docker】Docker 容器日志过大导致磁盘爆满
  • Linux 内核系统架构
  • Kafka-Consumer源码分析
  • USB 声卡全解析:提升音频体验的得力助手
  • 网络安全之常用安全设备功能及作用_设备管理器安全设备是什么
  • Runway 技术浅析(六):文本到视频(Text-to-Video)
  • GPT时代的BI革命:智能报表系统如何颠覆传统决策
  • qt音频实战
  • Vue 实现无线滚动效果
  • Linux下anaconda安装环境
  • Docker和Docker Compose部署方式的区别以及各自适用的场景(ChatGPT-4o回答)
  • WPF+MVVM案例实战与特效(三十一)- 封装一个加载动画的自定义控件
  • 将一个数组逆序输出。-多语言
  • 【SQL】实战--组合两个表
  • 一、文本预处理
  • mysql order by后进行limit分页查询出现重复数据
  • shell脚本30个案例(五)
  • Spring AI 框架介绍
  • WuCup网络安全技能大赛WP
  • Java 单例模式:深度解析与应用
  • mysql线上问题集合