资讯专栏INFORMATION COLUMN

View之invalidate,requestLayout,postInvalidate

zhangyucha0 / 1679人阅读

摘要:因为是在线程的,所以方法的作用就是将非线程的刷新操作切换到线程,以便在线程中调用方法刷新。一句话总结方法的作用就是实现了消息机制,可以使我们在非线程也能调用刷新的方法。

目录介绍

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/yangchong2...

如果觉得好,可以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方法中的重点逻辑在源码上添加注释呢。

</>复制代码

  1. public void invalidate() {
  2. invalidate(true);
  3. }
  4. public void invalidate(boolean invalidateCache) {
  5. invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
  6. }
  7. void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
  8. boolean fullInvalidate) {
  9. if (mGhostView != null) {
  10. mGhostView.invalidate(true);
  11. return;
  12. }
  13. if (skipInvalidate()) {
  14. return;
  15. }
  16. if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
  17. || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
  18. || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
  19. || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
  20. if (fullInvalidate) {
  21. mLastIsOpaque = isOpaque();
  22. mPrivateFlags &= ~PFLAG_DRAWN;
  23. }
  24. mPrivateFlags |= PFLAG_DIRTY;
  25. if (invalidateCache) {
  26. mPrivateFlags |= PFLAG_INVALIDATED;
  27. mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
  28. }
  29. //这个地方是重点逻辑,主要分析这个
  30. // Propagate the damage rectangle to the parent view.
  31. final AttachInfo ai = mAttachInfo;
  32. final ViewParent p = mParent;
  33. if (p != null && ai != null && l < r && t < b) {
  34. final Rect damage = ai.mTmpInvalRect;
  35. damage.set(l, t, r, b);
  36. p.invalidateChild(this, damage);
  37. }
  38. // Damage the entire projection receiver, if necessary.
  39. if (mBackground != null && mBackground.isProjected()) {
  40. final View receiver = getProjectionReceiver();
  41. if (receiver != null) {
  42. receiver.damageInParent();
  43. }
  44. }
  45. }
  46. }

看看ViewGroup中的invalidateChild方法

在ViewGroup的invalidateChild方法中有一个循环,循环里面会一直调用父布局的invalidateChildInParent方法,而View和ViewGroup的最终父布局都是ViewRootImpl。

</>复制代码

  1. @UiThread
  2. public abstract class ViewGroup extends View implements ViewParent, ViewManager {
  3. @Override
  4. public final void invalidateChild(View child, final Rect dirty) {
  5. ViewParent parent = this;
  6. final AttachInfo attachInfo = mAttachInfo;
  7. if (attachInfo != null) {
  8. //这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环
  9. do {
  10. View view = null;
  11. if (parent instanceof View) {
  12. view = (View) parent;
  13. }
  14. //这里调用的是父布局的invalidateChildInParent方法
  15. parent = parent.invalidateChildInParent(location, dirty);
  16. } while (parent != null);
  17. }
  18. }
  19. @Override
  20. public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
  21. if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
  22. (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
  23. if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
  24. FLAG_OPTIMIZE_INVALIDATE) {
  25. ...
  26. //这里也是一些计算绘制区域的内容
  27. return mParent;
  28. } else {
  29. mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
  30. //这里也是一些计算绘制区域的内容
  31. return mParent;
  32. }
  33. }
  34. return null;
  35. }
  36. }

View中的invalidateChild方法和ViewGroup中的invalidateChildInParent方法最后殊途同归,都会调用到ViewRootImpl中的方法

可以看到在ViewRootImpl中最后都会调用scheduleTraversals方法进行绘制。按照对于View的绘制过程的理解,View的绘制流程是从ViewRootImpl的performTraversals方法开始的

</>复制代码

  1. public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
  2. //如果View没有父布局,那invalidateInternal方法就会调用这个方法
  3. @Override
  4. public void invalidateChild(View child, Rect dirty) {
  5. invalidateChildInParent(null, dirty);
  6. }
  7. //ViewGroup的invalidateChild方法最后会调用到这里
  8. @Override
  9. public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
  10. checkThread();
  11. //如果dirty为null就表示要重绘当前ViewRootImpl指示的整个区域
  12. if (dirty == null) {
  13. invalidate();
  14. return null;
  15. //如果dirty为empty则表示经过计算需要重绘的区域不需要绘制
  16. } else if (dirty.isEmpty() && !mIsAnimating) {
  17. return null;
  18. }
  19. return null;
  20. }
  21. private void invalidateRectOnScreen(Rect dirty) {
  22. final Rect localDirty = mDirty;
  23. ...
  24. if (!mWillDrawSoon && (intersected || mIsAnimating)) {
  25. //调用scheduleTraversals方法进行绘制
  26. scheduleTraversals();
  27. }
  28. }
  29. //绘制整个ViewRootImpl区域
  30. void invalidate() {
  31. mDirty.set(0, 0, mWidth, mHeight);
  32. if (!mWillDrawSoon) {
  33. //调用scheduleTraversals方法进行绘制
  34. scheduleTraversals();
  35. }
  36. }
  37. }

下面我们来看看ViewRootImpl中的scheduleTraversals方法

看到scheduleTraversals方法中调用了mChoreographer.postCallback方法

Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable。

</>复制代码

  1. void scheduleTraversals() {
  2. if (!mTraversalScheduled) {
  3. mTraversalScheduled = true;
  4. mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  5. mChoreographer.postCallback(
  6. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  7. if (!mUnbufferedInputDispatch) {
  8. scheduleConsumeBatchedInput();
  9. }
  10. notifyRendererOfFramePending();
  11. pokeDrawLockIfNeeded();
  12. }
  13. }

来看看TraversalRunnable这个类做了什么?

可以看到最终调用了performTraversals()方法进行绘制

</>复制代码

  1. final class TraversalRunnable implements Runnable {
  2. @Override
  3. public void run() {
  4. doTraversal();
  5. }
  6. }
  7. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  8. void doTraversal() {
  9. if (mTraversalScheduled) {
  10. mTraversalScheduled = false;
  11. mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
  12. if (mProfile) {
  13. Debug.startMethodTracing("ViewAncestor");
  14. }
  15. performTraversals();
  16. if (mProfile) {
  17. Debug.stopMethodTracing();
  18. mProfile = false;
  19. }
  20. }
  21. }

大概总结一下

invalidate方法最终调用的是ViewRootImpl中的performTraversals来重新绘制View

在自定义View时,当需要刷新View时,如果是在UI线程中,那就直接调用invalidate方法,如果是在非UI线程中,那就通过postInvalidate方法来刷新View

03.postInvalidate深入分析

先来看看View中的postInvalidate方法

</>复制代码

  1. @UiThread
  2. public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
  3. ...
  4. public void postInvalidate() {
  5. postInvalidateDelayed(0);
  6. }
  7. public void postInvalidate(int left, int top, int right, int bottom) {
  8. postInvalidateDelayed(0, left, top, right, bottom);
  9. }
  10. public void postInvalidateDelayed(long delayMilliseconds) {
  11. final AttachInfo attachInfo = mAttachInfo;
  12. if (attachInfo != null) {
  13. attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
  14. }
  15. }
  16. ...
  17. }

可以看到,postInvalidate方法最后调用了ViewRootImpl的dispatchInvalidateDelayed方法

ViewRootImpl中的dispatchInvalidateDelayed方法就是像ViewRootHandler发送了一个MSG_INVALIDATE消息,ViewRootHandler是ViewRootImpl中的一个内部Handler类

</>复制代码

  1. //发送消息
  2. //更多内容:https://github.com/yangchong211
  3. public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
  4. Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
  5. mHandler.sendMessageDelayed(msg, delayMilliseconds);
  6. }
  7. //接收消息
  8. final class ViewRootHandler extends Handler {
  9. @Override
  10. public String getMessageName(Message message) {
  11. switch (message.what) {
  12. case MSG_INVALIDATE:
  13. return "MSG_INVALIDATE";
  14. }
  15. return super.getMessageName(message);
  16. }
  17. @Override
  18. public void handleMessage(Message msg) {
  19. switch (msg.what) {
  20. case MSG_INVALIDATE:
  21. ((View) msg.obj).invalidate();
  22. break;
  23. }
  24. }
  25. }

大概总结一下

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方法。

</>复制代码

  1. //View.class
  2. @CallSuper
  3. public void requestLayout() {
  4. if (mMeasureCache != null) mMeasureCache.clear();
  5. if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
  6. // Only trigger request-during-layout logic if this is the view requesting it,
  7. // not the views in its parent hierarchy
  8. ViewRootImpl viewRoot = getViewRootImpl();
  9. if (viewRoot != null && viewRoot.isInLayout()) {
  10. if (!viewRoot.requestLayoutDuringLayout(this)) {
  11. return;
  12. }
  13. }
  14. mAttachInfo.mViewRequestingLayout = this;
  15. }
  16. //设置PFLAG_FORCE_LAYOUT标记位,这样就会导致重新测量和布局
  17. mPrivateFlags |= PFLAG_FORCE_LAYOUT;
  18. //设置PFLAG_INVALIDATED就会进行重新绘制
  19. mPrivateFlags |= PFLAG_INVALIDATED;
  20. if (mParent != null && !mParent.isLayoutRequested()) {
  21. //不断调用上层View的requestLayout方法
  22. mParent.requestLayout();
  23. }
  24. if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
  25. mAttachInfo.mViewRequestingLayout = null;
  26. }
  27. }

然后看看ViewRootImpl中的requestLayout方法

可以看到ViewRootImpl中的requestLayout方法中会调用scheduleTraversals方法,scheduleTraversals方法最后会调用performTraversals方法开始执行View的三大流程,会分别调用View的measure、layout、draw方法。

</>复制代码

  1. @Override
  2. public void requestLayout() {
  3. if (!mHandlingLayoutInLayoutRequest) {
  4. checkThread();
  5. mLayoutRequested = true;
  6. scheduleTraversals();
  7. }
  8. }

然后再看看measure测量方法

由于requestLayout方法设置了PFLAG_FORCE_LAYOUT标记位,所以measure方法就会调用onMeasure方法对View进行重新测量。在measure方法中最后设置了PFLAG_LAYOUT_REQUIRED标记位,这样在layout方法中就会执行onLayout方法进行布局流程。

</>复制代码

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  2. final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
  3. if (forceLayout || needsLayout) {
  4. // first clears the measured dimension flag
  5. mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
  6. int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
  7. if (cacheIndex < 0 || sIgnoreMeasureCache) {
  8. //调用onMeasure方法
  9. onMeasure(widthMeasureSpec, heightMeasureSpec);
  10. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  11. } else {
  12. long value = mMeasureCache.valueAt(cacheIndex);
  13. setMeasuredDimensionRaw((int) (value >> 32), (int) value);
  14. mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  15. }
  16. //设置PFLAG_LAYOUT_REQUIRED标记位,用于layout方法
  17. mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
  18. }
  19. }

再然后看看layout方法

由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以在layout方法中onLayout方法会被调用执行布局流程。最后清除PFLAG_FORCE_LAYOUT和PFLAG_LAYOUT_REQUIRED标记位。

</>复制代码

  1. public void layout(int l, int t, int r, int b) {
  2. //由于measure方法中设置了PFLAG_LAYOUT_REQUIRED标记位,所以会进入调用onLayout方法进行布局流程
  3. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  4. onLayout(changed, l, t, r, b);
  5. if (shouldDrawRoundScrollbar()) {
  6. if(mRoundScrollbarRenderer == null) {
  7. mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
  8. }
  9. } else {
  10. mRoundScrollbarRenderer = null;
  11. }
  12. //取消PFLAG_LAYOUT_REQUIRED标记位
  13. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  14. ListenerInfo li = mListenerInfo;
  15. if (li != null && li.mOnLayoutChangeListeners != null) {
  16. ArrayList listenersCopy =
  17. (ArrayList)li.mOnLayoutChangeListeners.clone();
  18. int numListeners = listenersCopy.size();
  19. for (int i = 0; i < numListeners; ++i) {
  20. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  21. }
  22. }
  23. }
  24. //取消PFLAG_FORCE_LAYOUT标记位
  25. mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  26. mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
  27. }

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.关于博客汇总链接

1.技术博客汇总

2.开源项目汇总

3.生活博客汇总

4.喜马拉雅音频汇总

5.其他汇总

02.关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn

github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/...

简书:http://www.jianshu.com/u/b7b2...

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo...

开源中国:https://my.oschina.net/zbj161...

泡在网上的日子:http://www.jcodecraeer.com/me...

邮箱:yangchong211@163.com

阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV

segmentfault头条:https://segmentfault.com/u/xi...

掘金:https://juejin.im/user/593943...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73845.html

相关文章

  • 03.AndroidView原理问题

    摘要:这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。从顶层父到子递归调用方法,方法又回调。 目录介绍 3.0.0.1 View的绘制需要经过哪些过程?有哪些常用回调方法?View的绘制流程的详细流程是怎样的? 3.0.0.2 View绘制流程,当一个TextView的实例调用setText()方法后执行了什么?请说一下原理…… 3.0.0.3 requestLayout()、...

    FrozenMap 评论0 收藏0

发表评论

0条评论

zhangyucha0

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<