ViewRootImpl
Measure
Layout
Draw
入口函数:performTraversals
Android 28源码:
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)
Measure
MeasureSpec: 高2位(mode)低30位(size)
onMeasure和measure的区别
Measure不可以被重写 负责进行测量的传递
onMeasure可以被重写,负责测量的具体实现
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
在这种情况下,performMeasure会调用两次
测量结束之后,开始进行布局
Layout
performLayout(lp, mWidth, mHeight);
mView.layout(0,0,mView,getMeasuredWidth(),mView.getMeasuredHeight)
特殊情况:如果在layout的过程中,还有其他的layout请求过来,那么就需要清除所有的标志位然后做一整套的请求,测量,布局去处理这个情况
View.layout->View.onLayout(changed, l ,t , r ,b)
View 当中的onLayout是一个空实现,可以提供给子类去重写
View的layout与measure不同,它是一个public的方法且不是final,可以由子类继承去重写
那我们看下ViewGroup的实现
在ViewGroup当中,layout是被final修饰,且也继承了View的这个方法的实现,也就是说,我们通过实现onLayout方法就可以指定我们自己的类的布局方式,因为layout中会调用到onLayout函数
在ViewGroup当中,onLayout被abstract修饰,证明ViewGroup的子类一定要去自己实现onLayout来进行具体的布局
对于View的子类和ViewGroup的子类我们分别来看一个具体的例子:
1.TextView
在TextView当中没有去重写layout,只是简单的重写了onLayout方法
2.RelativeLayout
ViewGroup的子类没办法重写layout函数,在onLayout中,RelativeLayout将子View通过循环遍历的方式取出,然后去让子View调用自己的layout方式去进行布局
onLayout的职责:它并不负责自己的布局,它的布局都是由它的爸爸去出发,它只负责自己子view 的布局调用
Draw
performDraw();
boolean canUseAsync = draw(fullRedrawNeeded);
drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingRequired, dirty, surfaceInsets)
canvas = mSurface.lockCanvas(dirty);
mView.draw(canvas)
onDraw(cavas){空实现,对自己进行绘制,需要子类自己去实现}
dispatchDraw(canvas);{空实现,对孩子进行绘制,需要子类自己去实现}
我们分别从View 和 ViewGroup的角度看下这相关的三个函数
Draw:传递数据
onDraw:绘制自身
dispatchDraw:绘制子View
View:
Draw 是public且不是fianl类型,子类可以继承去进行重写
onDraw是空实现,子类可以重写也可以不重写
dispatchDraw是空实现,子类可以重写也可以不重写
ViewGroup:
没有重写draw以及onDraw方法
重写了dispatchDraw方法
drawChild(canvas, child, drawingTime);
child.draw(canvas, this, drawingTime);
在ViewGroup中没有对三个函数做特殊限制也就是说,任何ViewGroup的子类都可以重写三个函数,或者直接使用这三个函数
RelativeLayout:
没有重写父类的三个方法
TextView:
重写了View的onDraw方法
requestLayout 和 invalidate 详解
View.requestLayout:
官方解释:
当视图的布局有改变的时候,刷新
它将会在视图树上遍历执行一边layout
这个不应该在视图层级正在layout的时候调用
如果layout操作正在执行,这个请求会被接受在当前的layout操作结束的时候,layout会被再次调用
或者在当前的帧被绘制完成然后下一次的layout操作发生的时候
子类如果想要重写这个方法的话,应该先调用父类的函数实现去正确的处理可能会发生的一些 布局期间请求的 错误。
1.如果测量缓存不为空,将测量缓存清空??
2.如果当前的根布局在布局过程中,return函数
3.如果爸爸不为空,而且爸爸已经布局结束,那么就调用爸爸的requestLayout
ViewGroup没有重写View的requestLayout方法。
重点:
这里有两个标志位的设置
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
也就是说这个是从子节点往根节点的逆序遍历调用过程
最终调用到的就是DecorView ,那么我们看下DecorView的爸爸是谁
view.assignParent(this);
那么就是说,DecorView的爸爸就是ViewRootImpl
ViewRootImpl.requestLayout
如果没有在处理layout请求的过程中
1.检查是不是主线程
2.将layout请求的flag设置为true
3.调用scheduleTraversals
1.mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
performTraversals();
又重新调回了performTrarsals,重新开始了 measure/layout/draw整个流程
那么是不是所有的子View都需要测量呢??这时候标志位就派上用场了
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
…
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
所以只有调用了requestlayout的View才会进行重新测量
在View.layout中,在调用了onLayout之后有一个这样的代码
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
标志位已经恢复
在进行完layout之后,标志位复原
在draw函数中,
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
重点看这个dirtyOpaque
if (!dirtyOpaque) {
drawBackground(canvas);
}
if (!dirtyOpaque) onDraw(canvas);
dirtyOpaque == TRUE的时候,不会调用自身的绘制方法,子View也雷同
所以requestLayout
1.由子View调用爸爸的requestLayout,一直到ViewRootImpl为止,它的requestLayout又调用到了performTraversals函数,开始了 measure,layout,draw由于在layout的时候,标志位恢复,导致draw就不会被执行,也就是说requestLayout就到layout函数为止就结束了。
invalidate()
官方解释:
重新绘制全部的View,如果这个View是可见的,onDraw就会在未来的某个点被调用,这个函数一定要从UI线程调用,如果在非UI线程调用的话,调用postInvalidate方法
invalidate(true);
public void invalidate(boolean invalidateCache) {
官方解释:
这个函数是invalidate实际开始进行工作的地方一个完整的invalidate函数调用造成视图缓存被重绘,但是这个方法可以通过高设置
invalidateCache参数为false跳过这些重绘的步骤对于那些不需要的情况,例如一个组件的内容和大小都没变化
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
1.如果需要跳过重绘,跳过
return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
2.p.invalidateChild(this, damage);
ViewGroup.invalidateChild(@Deprecated) instead of onDescendantInvalidated
// HW accelerated fast path
onDescendantInvalidated(child, child);
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
So,这个方法也最后会调用到最后一个爸爸的onDescendantInvalidated这个方法,那我们直接到ViewRootImpl去看下
@Override
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
Ok,看到这里优势很熟悉的代码啦,
scheduleTraversals这个函数最后仍然是调用到了performTraversals,请求重绘View的树,即draw过程,假如视图没有发生大小的变化的话就不回进行layout的过程,只是绘制需要绘制的那些视图
mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
是否执行onMeasure方法也和这个标志位有关系mPrivateFlags
是否执行onLayout也和这个标志位有关系mPrivateFlags
所以mPrivateFlags的变化是控制整个绘制流程的一个重点!!!!!!!!!
1 invalidate,请求重新draw,只会绘制调用者本身。
2 setSelected 也是请求invalidate,同上
*/
public void setSelected(boolean selected) {
//noinspection DoubleNegation
invalidate(true);
}
}
3.setVisibility
当View从INVISIBLE变为VISIBILE,会间接调用invalidate方法,继而绘制该View,而从INVISIBLE/VISIBLE变为GONE之后,由于View树的大小发生了变化,会进行measure/layout/draw,同样,他只会绘制需要重绘的视图。
if (newVisibility == VISIBLE) {
invalidate(true);
}
/* Check if the GONE bit has changed */
if ((changed & GONE) != 0) {
requestLayout();
((View) mParent).invalidate(true);
}
if ((changed & INVISIBLE) != 0) {
/*
If this view is becoming invisible, set the DRAWN flag so that
the next invalidate() will not be skipped.
*/
mPrivateFlags |= PFLAG_DRAWN;
}
setEnable:请求重新draw,只会绘制调用者本身。invalidate(true);
requestFocus:请求重新draw,只会绘制需要重绘的视图。
补充学习:
1.http://blog.csdn.net/yanbober/article/details/46128379/
2.http://blog.csdn.net/a553181867/article/details/51583060
View中 getWidth 和 getMeasuredWidth 的区别
先看下getWidth:
/**
-
- Return the width of your view.*
*** @return The width of your view, in pixels.
- /
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
返回你视图的宽度,以像素为单位
这些坐标值是从onLayout中传递过来
有他的右边的坐标减去它的左边的坐标得出
getMeasuredWidth
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}测量阶段中计算出的宽度。
返回未经加工的测量的宽度
源头是从onMeasure传递进来
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
- getMeasuredWidth是measure阶段获得的View的原始宽度。
- getWidth是layout阶段完成后,其在父容器中所占的最终宽度
举一个例子
我们自定义一个lineaerlayout
不复写它的onMeasure方法,那么这里的测量结果一定是我们xml设置的值
在layout函数中我们在传入坐标的地方做一些手脚,那么最终显示是以getWidth的数据为准
child.layout(child.getLeft() ,child.getTop(), child.getRight() + 400, child.getBottom());