View之invalidate,requestLayout,postInvalidate
目录介绍
- 01.invalidate,requestLayout,postInvalidate区别
- 02.invalidate深入分析
- 03.postInvalidate深入分析
- 04.requestLayout深入分析
- 05.ViewRootImpl作用分析
- 06.这几个方法总结
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
- 01.Java基础问题(19个)
- 02.Java面向对象问题(10个)
- 03.Java数据结构问题(20个)
- 04.JavaIO流问题(6个)
- 05.java多线程问题(19)
- 06.Java虚拟机问题(10个)
- 07.Java类加载问题(8个)
- 08.Java反射问题(6个)
- 10.Java异常问题(9个)
01.requestLayout、invalidate与postInvalidate作用与区别
- invalidate() postInvalidate()
- 共同点:都是调用onDraw()方法,然后去达到重绘view的目的
- 区别:invalidate()用于主线程,postInvalidate()用于子线程【通过handler发送消息,在handleMessage中((View) msg.obj).invalidate(),】
- requestLayout()
- 也可以达到重绘view的目的,但是与前两者不同,它会先调用onLayout()重新排版,再调用ondraw()方法。
- 当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view(父类的视图)重新调用他的onMeasure、onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法requestLayout()。 requestLayout调用onMeasure和onLayout,不一定调用onDraw
02.invalidate深入分析
- 看看invalidate源码,如下所示
- invalidateCache为true表示全部重绘。View的invalidate方法最后调用的是invalidateInternal方法,invalidateInternal方法中的重点逻辑在源码上添加注释呢。
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) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
} if (skipInvalidate()) {
return;
} 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;
} 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);
} // Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
}
}
- 看看ViewGroup中的invalidateChild方法
- 在ViewGroup的invalidateChild方法中有一个循环,循环里面会一直调用父布局的invalidateChildInParent方法,而View和ViewGroup的最终父布局都是ViewRootImpl。
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
//这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
} //这里调用的是父布局的invalidateChildInParent方法
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
} @Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
...
//这里也是一些计算绘制区域的内容
return mParent;
} else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
//这里也是一些计算绘制区域的内容
return mParent;
}
}
return null;
}
}
- View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最后殊途同归,都会调用到ViewRootImpl中的方法
- 可以看到在ViewRootImpl中最后都会调用scheduleTraversals方法进行绘制。按照对于View的绘制过程的理解,View的绘制流程是从ViewRootImpl的performTraversals方法开始的
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks { //如果View没有父布局,那invalidateInternal方法就会调用这个方法
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
} //ViewGroup的invalidateChild方法最后会调用到这里
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
//如果dirty为null就表示要重绘当前ViewRootImpl指示的整个区域
if (dirty == null) {
invalidate();
return null;
//如果dirty为empty则表示经过计算需要重绘的区域不需要绘制
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
return null;
} private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
...
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
//调用scheduleTraversals方法进行绘制
scheduleTraversals();
}
} //绘制整个ViewRootImpl区域
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
//调用scheduleTraversals方法进行绘制
scheduleTraversals();
}
}
}
- 下面我们来看看ViewRootImpl中的scheduleTraversals方法
- 看到scheduleTraversals方法中调用了mChoreographer.postCallback方法
- Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
- 来看看TraversalRunnable这个类做了什么?
- 可以看到最终调用了performTraversals()方法进行绘制
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
} performTraversals(); if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
- 大概总结一下
- invalidate方法最终调用的是ViewRootImpl中的performTraversals来重新绘制View
- 在自定义View时,当需要刷新View时,如果是在UI线程中,那就直接调用invalidate方法,如果是在非UI线程中,那就通过postInvalidate方法来刷新View
03.postInvalidate深入分析
- 先来看看View中的postInvalidate方法
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { ... public void postInvalidate() {
postInvalidateDelayed(0);
} public void postInvalidate(int left, int top, int right, int bottom) {
postInvalidateDelayed(0, left, top, right, bottom);
} public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
...
}
- 可以看到,postInvalidate方法最后调用了ViewRootImpl的dispatchInvalidateDelayed方法
- ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler发送了一个MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一个内部Handler类
//发送消息
//更多内容:https://github.com/yangchong211
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
} //接收消息
final class ViewRootHandler extends Handler {
@Override
public String getMessageName(Message message) {
switch (message.what) {
case MSG_INVALIDATE:
return "MSG_INVALIDATE";
}
return super.getMessageName(message);
} @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
}
}
}
- 大概总结一下
- postInvalidate方法调用了ViewRootImpl中的dispatchInvalidateDelayed方法向ViewRootImpl中的ViewRootHandler发送一个消息,最后调用的还是View的invalidate方法。
- 因为ViewRootImpl是在UI线程的,所以postInvalidate方法的作用就是将非UI线程的刷新操作切换到UI线程,以便在UI线程中调用invalidate方法刷新View。所以如果我们自定义的View本身就是在UI线程,没有用到多线程的话,直接用invalidate方法来刷新View就可以了。而我们平时自定义View基本上都没有开起其他线程,所以这就是我们很少接触postInvalidate方法的原因。
- 一句话总结postInvalidate方法的作用就是:实现了消息机制,可以使我们在非UI线程也能调用刷新View的方法。
04.requestLayout深入分析
- 源码如下所示
- 在View的requestLayout方法中,首先会设置View的标记位,PFLAG_FORCE_LAYOUT表示当前View要进行重新布局,PFLAG_INVALIDATED表示要进行重新绘制。
- requestLayout方法中会一层层向上调用父布局的requestLayout方法,设置PFLAG_FORCE_LAYOUT标记,最终调用的是ViewRootImpl中的requestLayout方法。
//View.class
@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标记位,这样就会导致重新测量和布局
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
//设置PFLAG_INVALIDATED就会进行重新绘制
mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) {
//不断调用上层View的requestLayout方法
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
- 然后看看ViewRootImpl中的requestLayout方法
- 可以看到ViewRootImpl中的requestLayout方法中会调用scheduleTraversals方法,scheduleTraversals方法最后会调用performTraversals方法开始执行View的三大流程,会分别调用View的measure、layout、draw方法。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
- 然后再看看measure测量方法
- 由于requestLayout方法设置了PFLAG_FORCE_LAYOUT标记位,所以measure方法就会调用onMeasure方法对View进行重新测量。在measure方法中最后设置了PFLAG_LAYOUT_REQUIRED标记位,这样在layout方法中就会执行onLayout方法进行布局流程。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//调用onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} //设置PFLAG_LAYOUT_REQUIRED标记位,用于layout方法
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}
- 再然后看看layout方法
- 由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以在layout方法中onLayout方法会被调用执行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED标记位。
public void layout(int l, int t, int r, int b) {
//由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以会进入调用onLayout方法进行布局流程
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
} //取消PFLAG_LAYOUT_REQUIRED标记位
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
} //取消PFLAG_FORCE_LAYOUT标记位
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
05.ViewRootImpl作用分析
- 链接WindowManager和DecorView的纽带,另外View的绘制也是通过ViewRootImpl来完成的。
- 它的主要作用我的总结为如下:
- A:链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带。
- B:完成View的绘制过程,包括measure、layout、draw过程。
- C:向DecorView分发收到的用户发起的event事件,如按键,触屏等事件。
06.这几个方法总结
- requestLayout方法会标记PFLAG_FORCE_LAYOUT,然后一层层往上调用父布局的requestLayout方法并标记PFLAG_FORCE_LAYOUT,最后调用ViewRootImpl中的requestLayout方法开始View的三大流程,然后被标记的View就会进行测量、布局和绘制流程,调用的方法为onMeasure、onLayout和onDraw。
- invalidate方法我们分析过,它的过程和requestLayout方法方法很像,但是invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法,不会调用onMeasure和onLayout方法。
其他介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e
View之invalidate,requestLayout,postInvalidate的更多相关文章
- Android view中的requestLayout和invalidate方法
Android view中的requestLayout和invalidate方法 requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent v ...
- Android开发 View的UI刷新Invalidate和postInvalidate
Invalidate 正常刷新 /** * 使整个视图无效.如果视图可见, * {@link #onDraw(android.graphics.Canvas)} 调用此方法后将在后续的UI刷新里调用o ...
- Android界面刷新之invalidate与postInvalidate的区别
Android的invalidate与postInvalidate都是用来刷新界面的. 在UI主线程中,用invalidate():本质是调用View的onDraw()绘制. 主线程之外,用postI ...
- 解决:View调用invalidate()后不刷新onDraw()
近来学android图片处理,按照例子来,自定义一个View,之后在Activity里面手动调用该View的invalidate()后,一直无法刷新onDraw() 上网搜了一下,有两种解决办法: 一 ...
- Android View 深度分析requestLayout、invalidate与postInvalidate
前言 前几篇文章中,笔者对View的三大工作流程进行了详细分析,而这篇文章则详细讲述与三大工作流程密切相关的两个方法,分别是requestLayout和invalidate,如果对Viwe的三个工作流 ...
- 面试 -- requestLayout、invalidate与postInvalidate区别
requestLayout: 从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程.但是,真实情况略有不同,如果子View调用了这个方法, ...
- requestLayout, invalidate和postInvalidate的异同
requestLayout 当一个VIEW的布局属性发生了变化的时候,可以调用该方法,让父VIEW调用onmeasure 和onlayout重新定位该view的位置,需要在UI线程调用 invalid ...
- 转--Invalidate和postInvalidate的更新view区别
Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用. Android提供了Inva ...
- view, surfaceView, invalidate, postInvalidate, 刷新屏幕
http://blog.csdn.net/linghu_java/article/details/9985489 1.view view在api中的结构 Java.lang.Object Androi ...
- invalidate()和postInvalidate() 的区别及使用
Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中 ...
随机推荐
- Delphi 异常处理 详解
[1] Exception类的定义在SysUtils单元中. [2] Delphi也支持不从Exception继承的异常类,但是我觉得这么做并不十分的明智. 一.异常的源 在Delphi的应用程序中, ...
- IntelliJ IDEA 2023.1永久激活方法
IntelliJ IDEA永久激活方法 以下为破解教程: 注意:适用于IntelliJ IDEA 2021.3及其以上版本. 1. 清空IDEA以前的激活方法 大家可能在网上找了很多破解方法,比如修改 ...
- 扩展说明: 指令微调 Llama 2
这篇博客是一篇来自 Meta AI,关于指令微调 Llama 2 的扩展说明.旨在聚焦构建指令数据集,有了它,我们则可以使用自己的指令来微调 Llama 2 基础模型. 目标是构建一个能够基于输入内容 ...
- 【CSS】如何复原被隐藏的滚动条?记一个看似简单的样式问题所引发的一系列思考
壹 ❀ 引 故事的起因是这样的,某一个同事在封装了一个TableList组件,用于做表格视图渲染,但出于研发经验考虑上,他可能觉得表格若出滚动条可能会引发某些不可预估的小问题(毕竟一个基础组件会被用于 ...
- JS Leetcode 155. 最小栈 题解分析
壹 ❀ 引 本题来自LeetCode155. 最小栈,难度简单,题目描述如下: 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x ...
- JS leetcode 找到所有数组中消失的数字 题解分析
壹 ❀ 引 十天前做的一道题了,一直没整理,今天才花时间去读了官方题解思路,这道题也凸显出了算法思路的重要性,执行耗时差的真不是一点半点.题目来自448. 找到所有数组中消失的数字,题目描述如下: 给 ...
- NC16810 [NOIP1999]拦截导弹
题目链接 题目 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度.某天,雷达 ...
- 【译】IEEE白皮书 6G 太赫兹技术的基本原理 2023版
第一章 简介 太赫兹波是介于微波和光波之间的光谱区域,频率从 0.1THz ~ 10THz 之间,波长在 3mm ~ 30μm 之间.提供大块连续的频带范围以满足对 Tbit/s 内极高数据传输速率的 ...
- spring源码学习之设计模式
目录 1.策略模式 2.观察者模式 3.装饰者模式 4.工厂模式 工厂方法模式 抽象工厂模式 工厂方法和抽象工厂的异同 5.单例模式 6.适配器模式与外观模式 适配器模式 外观模式 7.模板方法模式 ...
- 【Android逆向】frida 破解 滚动的天空
1. apk 安装到手机中 2. 玩十次之后,会提示 充值 3. adb shell dumpsys window | grep mCurrentFocus 查看一些当前activity是哪一个 是 ...