目录介绍

  • 01.invalidate,requestLayout,postInvalidate区别
  • 02.invalidate深入分析
  • 03.postInvalidate深入分析
  • 04.requestLayout深入分析
  • 05.ViewRootImpl作用分析
  • 06.这几个方法总结

好消息

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.关于我的博客

View之invalidate,requestLayout,postInvalidate的更多相关文章

  1. Android view中的requestLayout和invalidate方法

    Android view中的requestLayout和invalidate方法 requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent v ...

  2. Android开发 View的UI刷新Invalidate和postInvalidate

    Invalidate 正常刷新 /** * 使整个视图无效.如果视图可见, * {@link #onDraw(android.graphics.Canvas)} 调用此方法后将在后续的UI刷新里调用o ...

  3. Android界面刷新之invalidate与postInvalidate的区别

    Android的invalidate与postInvalidate都是用来刷新界面的. 在UI主线程中,用invalidate():本质是调用View的onDraw()绘制. 主线程之外,用postI ...

  4. 解决:View调用invalidate()后不刷新onDraw()

    近来学android图片处理,按照例子来,自定义一个View,之后在Activity里面手动调用该View的invalidate()后,一直无法刷新onDraw() 上网搜了一下,有两种解决办法: 一 ...

  5. Android View 深度分析requestLayout、invalidate与postInvalidate

    前言 前几篇文章中,笔者对View的三大工作流程进行了详细分析,而这篇文章则详细讲述与三大工作流程密切相关的两个方法,分别是requestLayout和invalidate,如果对Viwe的三个工作流 ...

  6. 面试 -- requestLayout、invalidate与postInvalidate区别

    requestLayout: 从方法名字可以知道,“请求布局”,那就是说,如果调用了这个方法,那么对于一个子View来说,应该会重新进行布局流程.但是,真实情况略有不同,如果子View调用了这个方法, ...

  7. requestLayout, invalidate和postInvalidate的异同

    requestLayout 当一个VIEW的布局属性发生了变化的时候,可以调用该方法,让父VIEW调用onmeasure 和onlayout重新定位该view的位置,需要在UI线程调用 invalid ...

  8. 转--Invalidate和postInvalidate的更新view区别

    Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用. Android提供了Inva ...

  9. view, surfaceView, invalidate, postInvalidate, 刷新屏幕

    http://blog.csdn.net/linghu_java/article/details/9985489 1.view view在api中的结构 Java.lang.Object Androi ...

  10. invalidate()和postInvalidate() 的区别及使用

    Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中 ...

随机推荐

  1. 从零开始的微信小程序入门教程(四),理解小程序事件与冒泡机制

    壹 ❀ 引 我在之前初识WXML与数据绑定两篇文章中,介绍了小程序静态模板与样式相关概念,以及小程序几种常用数据绑定方式,在知道这些知识后,我们可以写一些不算复杂的小程序页面,并能将一些自定义的数据渲 ...

  2. git基本操作(二)

    分支(git branch) git branch 命令用于列出,创建或删除分支. git branch -a git branch git branch -v # 查看每一个分支上的最后一次comm ...

  3. UVALive7146 Defeat the Enemy

    题目链接 题目 见链接. 题解 知识点:贪心,STL. 首先要保证我方军队能消灭对方军队才行,因此只要我们按攻击力从大到小排,对方按防御力从大到小排,从大到小遍历,用我方所有攻击力大于敌方目前防御力军 ...

  4. NC16856 [NOI1999]钉子和小球.md

    题目链接 题目 题目描述 有一个三角形木板,竖直立放,上面钉着n(n+1)/2颗钉子,还有(n+1)个格子(当n=5时如图1).每颗钉子和周围的钉子的距离都等于d,每个格子的宽度也都等于d,且除了最左 ...

  5. 【Android】使用AIDL实现进程间传递对象案例

    1 前言 ​ 在 Android--使用AIDL实现进程间通讯简单案例 中介绍了使用 AIDL 在进程间传递字符串,对于8种基本数据类型( byte.short.int.long.float.doub ...

  6. Spring Boot图书管理系统项目实战-5.读者管理

    导航: pre:  4.基础信息管理 next:6.图书管理 只挑重点的讲,具体的请看项目源码. 1.项目源码 需要源码的朋友,请捐赠任意金额后留下邮箱发送:) 2.页面设计 <!DOCTYPE ...

  7. 责任链模式与spring容器的搭配应用

    背景 有个需求,原先只涉及到一种A情况设备的筛选,每次筛选会经过多个流程,比如先a功能,a功能通过再筛选b功能,然后再筛选c功能,以此类推.现在新增了另外一种B情况的筛选,B情况同样需要A情况的筛选流 ...

  8. 李宏毅2022机器学习HW3 Image Classification

    Homework3 数据集下载 在本地环境下进行实验总是令人安心,但是又苦于网上找不到数据集,虽然kaggle上有数据集但是下载存在问题 于是有了一个天才的想法,间接从kaggle上下载(利用outp ...

  9. win32 - QueryDisplayConfig的使用

    QueryDisplayConfig函数检索关于所有显示设备的所有可能的显示路径,或视图,在当前设置的信息. C++样本: (开箱即用) 代码列出了所有显示器的名称和拓展模式 #include < ...

  10. Direct2D 旋转篇

    微软文档:Transforms 本篇通过官方文档学习,整理出来的demo,初始样本请先创建一个普通的desktop app. ID2D1SolidColorBrush* m_pOriginalShap ...