跳至主要內容

Android View绘制机制

PPLong大约 8 分钟学习

Android View绘制机制

View绘制

基本

ViewRoot

DecorView

顶层ViewGroup,FrameLayout子类

View根布局关系图
View根布局关系图

注意这里是View的三步骤不是连续执行的,而是基于每一步骤大过程分开执行的,先总体执行measure再layout再draw

ViewRoot绘制流程图
ViewRoot绘制流程图

MeasureSpec

决定View的测量过程,测量过程中,系统将LayoutParam所加的约束转换为MeasureSpec,根据改MeasureSpec测量View的宽高

组成:32位int值,高2位表示SpecMode, 低30位代表SpecSize

模式

  • UNSPECIFIED:父容器不对View有任何限制,要多大给多大
  • EXACTLY:父容器已经测出View所需要的精确大小,View最终大小就是SpecSize所指定的值,对应于Layoutparams中的 指定值以及MATCH_PARENT
  • AT_MOST:父容器内指定一个可用大小SpecSize,View大小不能大于这个值,对应WRAP_CONTENT

DecorView绘制流程

// Activity
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
// 在Activity启动时通过ActivityThread performLaunchActivity中调用
final void attach(Context context, ...) {
    // 初始化mWindow,实例为PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mUiThread = Thread.currentThread();
    mMainThread = aThread;
    mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),
        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
// PhoneWindow
@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    // 设置自定义布局界面在content中
    mLayoutInflater.inflate(layoutResID, mContentParent);
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        // 回调, 通知内容改变
        cb.onContentChanged();
    }
}

private void installDecor() {
    if (mDecor == null) {
        // 生成DecorView
        mDecor = generateDecor(-1);
		// ...
    } 
    // 初始化mContentParent为
    mContentParent = generateLayout(mDecor);
}

protected ViewGroup generateLayout(DecorView decor) {
	int layoutResource;
    mDecor.startChanging();
    // 根据样式设置DecorView内容
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    // 绑定mContentParent(自定义View所在的主界面)
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    return contentParent;
}

// DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    final View root = inflater.inflate(layoutResource, null);
    // 判断逻辑,并addView进行初始化(核心)
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
                                  new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
}

那么自定义布局在DecorView中便加载好了,接下来如何显示DecorView呢?

// ActivityThread
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest, boolean isForward, String reason) {
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        // 设置DecorView不可见
        decor.setVisibility(View.INVISIBLE);
        // 添加DecorView到WindowManager中
        wm.addView(decor, l);
    }
   if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
       // 设置DecorView为VISBILE
       r.activity.makeVisible();
   }
}

void makeVisible() {
    // DecorView还没有被添加到WindowManager中
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    // 设置DecorView为VISIBLE
    mDecor.setVisibility(View.VISIBLE);
}

// WindowManagerImpl
// 在Android31版本中此处代码为空, 不知道通过什么步骤创建的WindowManager,因此这里采用Android28版本代码
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ViewRootImpl root;
    synchronized (mLock) {
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
        // 构建ViewRootImpl对象
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        // 添加view、root到指定ArrayList中
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        // 核心方法
        root.setView(view, wparams, panelParentView);
    }
}

// ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        // ...
        requestLayout();
        // ...
    }
}

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
		// 检查是否在主线程中
        checkThread();
        mLayoutRequested = true;
        // 
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 通过Handler异步地发送Runnable操作
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

// mTraversalRunnable为TraversalRunnable,只做了一件事:doTraversal()
// ViewRootImpl
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // 核心方法,进行View绘制的三大流程
        performTraversals();
    }
}

performTraversals()做了以下几件事:

  • 测量窗口大小
  • 调用performMeasure,实际调用测mView.measure方法
  • performDraw

Measure

View

// View

// widthMeasureSpec – Horizontal space requirements as imposed by the parent 
// heightMeasureSpec – Vertical space requirements as imposed by the parent
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 缓存判断, 此处对View宽高有缓存,并在需要重新测绘时会比较
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        // 核心方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

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

// 存储测量后得到的宽高值
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { 
    // ... 
}
// 根据View的测量规格来测量View的宽或高
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
        // UNSPECIFIED:使用默认大小,即getSuggestedMinimumWidth或getSuggestedMinimumHeight
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        // AT_MOST,EXACTLY:使用View测量后的宽高值
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

protected int getSuggestedMinimumWidth() {
    // 若View无背景,则View的宽度为android:minWidth指定的
    // 若View有背景,则View的宽度为android:minWidth与背景Drawable宽度的最大值
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

ViewGroup

由于不同的ViewGroup具有不同的布局特性,因此各自的子View测量方法不同,所以onMeasure方法各有不同

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

// 取出child的layoutParam,再计算得到MeasureSpec进而进行测量
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);
}

View的measure过程是三大流程中最复杂的一个,measure完成后,通过getMeasuredWith/Height能获取到View的正确测量宽高,但在一些情况下,系统可能需要多次measure才能确定最终测量宽高,因此最好不要在onMeasure方法中拿宽高,而是在onLayout去获取

Activity周期与View Measure问题

以前常犯的一个错误就是经常在onCreate中去获取View的宽高,有时经常显示宽高为0。这是因为View的measure过程与Activity生命周期不是同步的,回顾之前的代码,doTraversal是通过Handler去异步启动的,所以导致了不同步现象,那如何正确获取到View宽高呢?

  1. onWindowFocusChanged
  2. view.post
  3. ViewTreeObserver
  4. view.measure

为什么onMeasure会执行多次

Layout

View

public void layout(int l, int t, int r, int b) {
    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);
	// 视图大小、位置发生改变,重新layout确定view位置
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
    }
}

// 保存View四个顶点位置
// Returns: true if the new size and position are different than the previous ones @hide
protected boolean setFrame(int left, int top, int right, int bottom) {
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
}

// setOpticalFrame:追加效果边距长度

// 单一View是没有子View的,且layout中已经对自身View进行了位置计算,因此不需要再计算了
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
View layout流程
View layout流程

ViewGroup

ViewGroup Layout流程
ViewGroup Layout流程

ViewGroup同样也只有抽象方法onLayout,因此继承ViewGroup类时必须重写onLayout方法

分析一下LinearLayout中的onLayout方法

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

// 假设是Vertical布局
void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();
	// 获取Layout Gravity
    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

    switch (majorGravity) {
        case Gravity.BOTTOM:
            // mTotalLength contains the padding already
            childTop = mPaddingTop + bottom - top - mTotalLength;
            break;

            // mTotalLength contains the padding already
        case Gravity.CENTER_VERTICAL:
            childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
            break;

        case Gravity.TOP:
        default:
            childTop = mPaddingTop;
            break;
    }

    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
            // 如果VISBILITY不是GONE,则计算child位置
        } else if (child.getVisibility() != GONE) {
            // 获取child 宽高
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp =
                (LinearLayout.LayoutParams) child.getLayoutParams();

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            // 根据Child layout_gravity判断child布局
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {

                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                        + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }
		   // 判断是否有Divier
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            // 等于调用child.layout
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                          childWidth, childHeight);
            // childTop逐渐增大,后续的子元素会放在靠下位置
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

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

getHeight与getMeasuredHeight有什么不一样?

在非人为情况下,其值永远是相等的

Draw

View

public void draw(Canvas canvas) {
	// 绘制背景
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
    if (!verticalEdges && !horizontalEdges) {
        // 绘制View本身内容
        if (!dirtyOpaque) {
            onDraw(canvas);
        }
        // 分发子View Draw事件
        dispatchDraw(canvas);
        // 绘制装饰:前景、滑动条
        onDrawForeground(canvas);
        // 绘制默认高亮
        drawDefaultFocusHighlight(canvas);
    }
}

private void drawBackground(Canvas canvas) {
    final Drawable background = mBackground;
    if (background == null) {
        return;
    }
    // 设置Drawable边界限制
    setBackgroundBounds();

    // 绘制背景Drawbale, 可能会有偏移值
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
        background.draw(canvas);
    } else {
        canvas.translate(scrollX, scrollY);
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}

// 不同View不同实现
protected void onDraw(Canvas canvas) {}

// View中无子View,不需要Dispatch
protected void dispatchDraw(Canvas canvas) {}

ViewGroup

不同Layout中可能重写onDraw方法,进行基于布局特性的绘制

ViewGroup绘制流程
ViewGroup绘制流程
@Override
protected void dispatchDraw(Canvas canvas) {
    final View[] children = mChildren;
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                transientChild.getAnimation() != null) {
                // 绘制子View逻辑
                more |= drawChild(canvas, transientChild, drawingTime);
            }
        }
    }
}

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

自定义View中需要注意的问题

支持特定属性

WRAP_CONTENT

重写onMeasure方法并设置默认宽高,当LayoutParams宽高为WRAP_CONTENT时,setMeasuredDimension(mWidth, mHeight);

paddling与margin
动画效果内存泄露

view.onDetachedFromWindow时进行动画取消

处理滑动冲突