目录
- View/ViewGroup总体的绘制流程是怎么样的
- ViewGroup为什么不走onDraw
- getMeasureWidth跟getWidth是不是一样的
- 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?
- 直接调用
setWillNotDraw(boolean willNotDraw)
方法 - 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()是一样的,但有些情况是不一致的:
- measure过程可能会经历多次,每次测量的结果可能不一样
- 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