在大家都了解过Android View的测量、布局、绘制机制后,我们来细化地分析一下关于View的重绘invalidate与更新requestLayout
现象
public class CustomEmptyView extends View {
public CustomEmptyView(Context context) {
super(context);
}
public CustomEmptyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomEmptyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("CustomEmptyView", "onMeasure");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.i("CustomEmptyView", "onLayout");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("CustomEmptyView", "onDraw");
}
}
从View的绘制机制可知,View从测量、布局、绘制的步骤中会对应执行该View#onMeasure()、View#onLayout()、View#onDraw()。那么我们今天讨论的View#invalidate()和View#requestLayout()呢?我们打印一下数据。
View#invalidate()的执行步骤是:
2019-03-26 17:32:34.739 8075-8075/com.example.myapplication I/CustomEmptyView: onDraw
View#requestLayout()的执行步骤是:
2019-03-26 17:33:13.497 8075-8075/com.example.myapplication I/CustomEmptyView: onMeasure
2019-03-26 17:33:13.501 8075-8075/com.example.myapplication I/CustomEmptyView: onLayout
2019-03-26 17:33:13.503 8075-8075/com.example.myapplication I/CustomEmptyView: onDraw
从打印数据来推测就是View#invalidate()只会执行View#onDraw();而View#requestLayout()则会重新走View的绘制流程。接下来我们从源码的角度来分析一下。
下面的源码分析基于android-28
View#requestLayout()
我们分析一下View#requestLayout()。我们定位到对应的源码
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree. This should not be called while the view hierarchy is currently in a layout
* pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
* end of the current layout pass (and then layout will run again) or after the current
* frame is drawn and the next layout occurs.
*
* 当某内容发生更改,导致该视图的布局重绘时调用此函数。这将安排视图树的布局传递。
* 当视图层次结构当前处于布局Layout事件时,不会执行该函数.
* 如果正在进行布局,可以在当前布局传递结束时(然后布局将再次运行)或在绘制当前帧并执行下一个布局之后执行请求。
* <p>Subclasses which override this method should call the superclass method to
* handle possible request-during-layout errors correctly.</p>
*/
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
// 该方法不在布局事件期间中执行
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
// PFLAG_FORCE_LAYOUT会在执行View的measure()和layout()方法时判断,这个是以前的文章看到的。
// 但是在当前源码的View.class和ViewRootImpl.class,全局搜索PFLAG_FORCE_LAYOUT,并没有直接的判断,导致View#requestLayout()不执行测量和布局方法
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
// isLayoutRequested()对应是mLayoutRequested字段,该字段在默认为false
if (mParent != null && !mParent.isLayoutRequested()) {
// 执行父容器的requestLayout()方法
mParent.r