View/ViewGroup 绘制流程和疑惑(二)详解手机开发

View/ViewGroup 绘制流程和疑惑(一)

View/ViewGroup 绘制流程和疑惑(二)

目录

  1. View/ViewGroup总体的绘制流程是怎么样的
  2. ViewGroup为什么不走onDraw
  3. getMeasureWidth跟getWidth是不是一样的
  4. requestLayout、invalidate、postInvalidate有什么不一样

问题

1. View/ViewGroup总体的绘制流程是怎么样的

上一篇文章《View/ViewGroup 绘制流程和疑惑(一)》已经大致走完了View/ViewGroup的绘制流程,这里可以用一张图解释
大致流程

2.ViewGroup为什么不走onDraw

在ViewGroup的源码中,有个初始化函数initViewGroup()

private void initViewGroup() {
    
    // ViewGroup doesn't draw by default 
    if (!debugDraw()) {
    
        setFlags(WILL_NOT_DRAW, DRAW_MASK); 
    } 
    ... 
} 

在上文的View.draw()中,有一段控制是否onDraw()的代码

boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 
// 如果脏区域不是实心的 
if (!dirtyOpaque) onDraw(canvas);  

可见,ViewGroup默认是不走onDraw(),如何才能让ViewGroup走onDraw?

  1. 直接调用setWillNotDraw(boolean willNotDraw)方法
  2. setBackgroud()设置背景
    // View.java 
    // 设置背景,mPrivateFlags去除掉PFLAG_SKIP_DRAW, 
    // PFLAG_SKIP_DRAW跟WILL_NOT_DRAW是一样的数值 
    public void setBackgroundDrawable(Drawable background) 
    	if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
          
        	mPrivateFlags &= ~PFLAG_SKIP_DRAW; 
        	requestLayout = true; 
    	} 
    

setBackgroundDrawable()中将mPrivateFlags去除掉PFLAG_SKIP_DRAW,PFLAG_SKIP_DRAW跟WILL_NOT_DRAW值是一样的,在draw()流程中就会走ondraw()

boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 
// 如果脏区域不是实心的 
if (!dirtyOpaque) onDraw(canvas);  
2.getMeasureWidth跟getWidth

先看下getMeasureWidth()getWidth()方法

    public final int getMeasuredWidth() {
    
        return mMeasuredWidth & MEASURED_SIZE_MASK; 
    } 

mMeasureWidth在measure过程中设置,getMeasureWidth()是测量高度

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

mRight、mLeft是在layout过程中设置,getWidth()是最终高度

总结一下,getMeasureWidth()是在measure中设置,getWidth()是在layout中设定的,大部分情况下getMeasureWidht()跟getWidth()是一样的,但有些情况是不一致的:

  1. measure过程可能会经历多次,每次测量的结果可能不一样
  2. layout过程修改了,得出的测量宽高有可能和最终宽高不一致
4. requestLayout、invalidate、postInvalidate有什么不一样
(1). invalidate

表示重新绘制,需要在UI线程调用,一起来看一下invalidate是如何起作用的

//View.java 
public void invalidate() {
    
    invalidate(true); 
} 
 
public void invalidate(boolean invalidateCache) {
    
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); 
} 
 
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, 
            boolean fullInvalidate) {
    
	... 
	mPrivateFlags |= PFLAG_DIRTY; 
	... 
   if (invalidateCache) {
    
         mPrivateFlags |= PFLAG_INVALIDATED; 
         mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; 
   } 
   // 往上找父容器,重新绘制 
   final ViewParent p = mParent; 
   if (p != null && ai != null && l < r && t < b) {
    
       final Rect damage = ai.mTmpInvalRect; 
       damage.set(l, t, r, b); 
       p.invalidateChild(this, damage); 
   } 
} 

invalidateChild中会逐层的往上寻找,最后会找到ViewRootImpl中

// ViewGroup.java 
public final void invalidateChild(View child, final Rect dirty) {
    
   do {
    
      ... 
 	  parent = parent.invalidateChildInParent(location, dirty); 
 	  ... 
   } while (parent != null); 
} 
 
// ViewRootImpl.java 
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    
    checkThread();//检查UI线程 
	 ... 
	 invalidateRectOnScreen(dirty); 
} 
 
private void invalidateRectOnScreen(Rect dirty) {
     
	final Rect localDirty = mDirty; 
	localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); 
	if (!mWillDrawSoon && (intersected || mIsAnimating)) {
    
        scheduleTraversals(); 
   } 
} 

invalidate最终还是来到了顶层的ViewRootImpl,修改了mDirty脏区域,调用scheduleTraversals(),最后还是会走到performTraversals()。

// ViewRootImpl.java 
private void performTraversals() {
    
    final View host = mView; 
    WindowManager.LayoutParams lp = mWindowAttributes; 
    ... 
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); 
    if (layoutRequested) {
   // 确认绘制需求 
        final Resources res = mView.getContext().getResources(); 
        // 确认window的size是否有改变 
        windowSizeMayChange |= measureHierarchy(host, lp, res, 
                    desiredWindowWidth, desiredWindowHeight); 
    } 
    ... 
    // Activity处于停止 
    if (!mStopped || mReportNextDraw) {
    
         // 判断是否需要重新测量 
         if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() 
             || mHeight != host.getMeasuredHeight() || contentInsetsChanged  
             || updatedConfiguration) {
    
                         
             int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
             int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
             //执行测量操作 
             performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
         } 
    } 
    ... 
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); 
    if (didLayout) {
    
    	 // 执行布局 
        performLayout(lp, mWidth, mHeight); 
    } 
    ... 
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;  
    if (!cancelDraw && !newSurface) {
    
    	 // 执行绘制 
        performDraw(); 
    } 
} 

mLayoutRequested 用来表示是否 measure 和 layout,而invalidate并没有修改mLayoutRequested ,所以不会走measure和layout流程,只会走到draw流程,最终根据脏区域仅绘制需要更新的view。

但如果是ViewGroup的invalidate,通过dispatchDraw()就会引发对子view进行重绘

在这里插入图片描述

(2). postInvalidate

postInvalidate 内部实现

    public void postInvalidate() {
    
        postInvalidateDelayed(0); 
    } 

postInvalidateDelayed通过Handler将切换到UI线程执行`invalidate()

(3). requestLayout

直接看下requestLayout核心代码

//View.java 
public void requestLayout() {
    
        if (mMeasureCache != null) mMeasureCache.clear(); 
        mPrivateFlags |= PFLAG_FORCE_LAYOUT; 
        mPrivateFlags |= PFLAG_INVALIDATED; 
    public final int getWidth() {
    
        return mRight - mLeft; 
    } 
        if (mParent != null && !mParent.isLayoutRequested()) {
    
            mParent.requestLayout(); 
        } 
    } 
 
    public void requestLayout() {
    
        if (!mHandlingLayoutInLayoutRequest) {
    
            checkThread();//检查UI线程 
            mLayoutRequested = true; 
            scheduleTraversals(); 
        } 
    } 

可见requestLayout还是会走到scheduleTraversals(),最后走到了performTraversals(),但是mPrivateFlags 设置了 PFLAG_FORCE_LAYOUT,会修改了mRequestLayout,所以内部会走measure、layout。

不一定会走draw流程,但往往会发现确实走draw流程,因为layout过程修改了l、t、r、b或者动画,触发一次invalidate,走到了draw。

可以参考:云图网

子View的requestLayout会触发父容器measure、layout,但是反过来就不一定触发子View的measure、layout

这里还发现了一个奇怪的现象,例如LinearLayout下面有a和b两个View,调用了a.requstLayout(),依次会调用a的measure()、onMeasure()、layout()、onLayout(),而b会有measure()、layout(),为什么b不会调用onMeasure()和onLayout()呢?

看一下view的measure()方法

// View.measure() 
 	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; 
            } 

上面有一个重要的变量mMeasureCache会存储上次测量的结果,当没有强制测量的时候,就会使用mMeasureCache避免一次重新测量,也就没有走onMeasure()

// View.layout() 
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); 

在View的layout()可以看到如果setFrame 没有发现改变,或者没有强制需要布局PFLAG_LAYOUT_REQUIRED,则不会走onLayout()

在这里插入图片描述

总结

图片描述

  • view调用invalidate流程,view会向parent往上寻找,不停调用parent的invalidateChildinParent, 最后会到达DecorView,触发ViewRootImpl的performTraversals(),因为没有设置mRequestLayout,所以不会走measure、layout流程,最后根据dirty区域仅仅绘制需要绘制的view

  • view调用requestLayout流程,view会向parent往上寻找,不停调用parent的requestLayout, 最后会到达DecorView,触发ViewRootImpl的performTraversals(),同时设置了mRequestLayout,触发onMeasure、onLayout流程,但是不一定会走onDraw,但如果layout过程发现位置变化了,会触发invalidate,走到onDraw

参考:

云图网
云图网
云图网

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6284.html

(0)
上一篇 2021年7月17日 00:45
下一篇 2021年7月17日 00:45

相关推荐

发表回复

登录后才能评论