Android 自定义控件 requestLayout / in
2021-02-05 本文已影响0人
科技猿人
Read The Fucking Source Code
引言
Android自定义控件涉及View的绘制分发流程
源码版本(Android Q — API 29)
本文涉及Android绘制流程
1. requestLayout
1.1 View的 requestLayout 过程
1.2 requestLayout自底向上
1.2.1 我们来看View中的requestLayout()方法。
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
//View树正在进行布局流程
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()) {
//ViewRootImpl是否会截获处理布局(已经在不居过程中),后面会有说明
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//View设置标记位,需要重新布局
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
//向父容器请求布局,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
1.2.2 我们来看ViewRootImpl中的requestLayoutDuringLayout()方法。
//返回值决定请求布局发起方是否进行递归布局申请,fase:中断布局请求;ture:布局请求继续执行。
boolean requestLayoutDuringLayout(final View view) {
if (view.mParent == null || view.mAttachInfo == null) {
// Would not normally trigger another layout, so just let it pass through as usual
return true;
}
//正在布局的视图包含请求view,则添加
if (!mLayoutRequesters.contains(view)) {
mLayoutRequesters.add(view);
}
//是否允许在不居中申请布局请求
if (!mHandlingLayoutInLayoutRequest) {
// Let the request proceed normally; it will be processed in a second layout pass
// if necessary
return true;
} else {
// Don't let the request proceed during the second layout pass.
// It will post to the next frame instead.
return false;
}
}
1.2.3 ViewGroup中没有复写requestLayout方法,所以ViewGroup也是调用和View同样的方法。
1.2.4 我们来看ViewRootImpl中的requestLayout()方法。
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//线程检查
checkThread();
mLayoutRequested = true;
//请求注册Vsync信号触发绘制
scheduleTraversals();
}
}
1.2.5 requestLayout()小结
requestLayout事件层层向上传递,直到DecorView(即根View),又会传递给ViewRootImpl(ViewRootImpl接管了DecorView的ViewParent功能),也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,作为View的外交大管家,只有ViewRootImpl能够处理requestLayout事件。
2. invalidate
2.1 View的 invalidate 过程
2.2 invalidate自底向上
2.2.1 我们来看View中的invalidate()方法。
public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
2.2.2 我们来看View中的invalidateInternal()方法。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
//代码省略……
//这里判断该子View是否可见或者是否处于动画中
if (skipInvalidate()) {
return;
}
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
mCachedContentCaptureSession = null;
//根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
//设置PFLAG_DIRTY标记位(增加脏区标记位)
mPrivateFlags |= PFLAG_DIRTY;
// 如果缓存失效,则清空对应标记
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
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);
}
//代码省略……
}
}
2.2.3 我们来看ViewGroup中的invalidateChild()方法。
@Deprecated(请注意这个废弃的标识,这个方法以后可能要废弃)
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
//如果硬件加速开启(默认硬件加速是开启的)
//现在能理解为什么这个方法标记未废弃方法了吧,因为进行了硬件加速的优化,具体细节以后在讲。
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
//进一步处理,用的是新的代替废弃方法的执行方法。
onDescendantInvalidated(child, child);
return;
}
//代码省略……
//如果硬件加速关闭,才会之前版本的递归遍历过程(此过程不讲了,网上大部分的博客还停留在这个版本阶段)
parent = parent.invalidateChildInParent(location, dirty);
//代码省略……
}
2.2.4 我们来看ViewGroup中的onDescendantInvalidated()方法。
//经过针对硬件加速优化后的代码,就显得清新脱俗了
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
/*
* HW-only, Rect-ignoring damage codepath
*
* We don't deal with rectangles here, since RenderThread native code computes damage for
* everything drawn by HWUI (and SW layer / drawing cache doesn't keep track of damage area)
*/
// if set, combine the animation flag into the parent
mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
// We lazily use PFLAG_DIRTY, since computing opaque isn't worth the potential
// optimization in provides in a DisplayList world.
mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
// simplified invalidateChildInParent behavior: clear cache validity to be safe...
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// ... and mark inval if in software layer that needs to repaint (hw handled in native)
if (mLayerType == LAYER_TYPE_SOFTWARE) {
// Layered parents should be invalidated. Escalate to a full invalidate (and note that
// we do this after consuming any relevant flags from the originating descendant)
mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
target = this;
}
//递归调用父类的onDescendantInvalidated,最终会调用到ViewRootImpl中的这个方法。
if (mParent != null) {
mParent.onDescendantInvalidated(this, target);
}
}
2.2.5 我们来看ViewRootImpl中的onDescendantInvalidated()方法。
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
// TODO: Re-enable after camera is fixed or consider targetSdk checking this
// checkThread();
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
//绘制方法
invalidate();
}
2.2.6 我们来看ViewRootImpl中的invalidate()方法。
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
//绘制请求,注册Vsync信号,等待上报绘制刷新。
scheduleTraversals();
}
}
2.2.7 invalidate小结
当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
2.2.8 postInvalidate简介
postInvalidate和invalidate的作用是一样的,唯一的区别是,postInvalidate可以在子线程中调用请求刷新UI。为什么呢?因为请求重新布局和绘制,最终都会在ViewRootImpl中处理,而ViewRootImpl会在请求方法中,进行线程检查(是否是UI线程)。当子view中有请求绘制的需求怎么办,那么就用postInvalidate。其实postInvalidate的功能就是 线程切换 + invalidate调用。
3 问题思考
requestLayout 和 invaldate 有什么区别?
- requestLayout 和 invalidate 都会触发整个绘制流程。但是在 measure 和 layout 过程中,只会对 flag 设置为 FORCE_LAYOUT 的情况进行重新测量和布局,而 draw 只会重绘 flag 为 dirty 的区域。
- requestLayout 是用来设置 FORCE_LAYOUT 标志,invalidate 用来设置 dirty 标志。所以 requestLayout 只会触发 measure 和 layout,invalidate 只会触发 draw。
- 所以一般都是组合使用。比如:只要刷新的时候就调用 invalidate,需要重新 measure 就调用 requestLayout,后面再跟个 invalidate(为了保证重绘)