Android View 绘制流程 与invalidate 和postInvalidate 分析--从源码角度

    整个View树的绘制流程是在ViewRootImpl.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为

 根之前状态,判断是否需要重新计算视图大小(measure)、是否重新需要布局视图的位置(layout)、以及是否需要重绘

 (draw),所以整个View的绘制过程,总结为三步:

1、  Measure:测量View大小

2 、 Layout:对View进行布局

3、  Draw:绘制View:View背景、View内容、View边线、绘制子View(如果有);


 一、performTraversals() 函数触发时机

  在这里提问下,为什么View的绘制是从performTraversals() 函数开始,这个函数是在什么时候触发的呢?

  来通过源码来分析下到底是什么原因,本文的源码是基于最新的源码6.0.1

  首先我们知道是通过 PhoneWindow的 setContentView 将View 加进来的,分析其源码如下:

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
 
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

然后调用到ViewGroup 的addView 方法:

/**
 * Adds a child view with the specified layout parameters.
 *
 * <p><strong>Note:</strong> do not invoke this method from
 * {
   @link #draw(android.graphics.Canvas)}, {
   @link #onDraw(android.graphics.Canvas)},
 * {
   @link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
 *
 * @param child the child view to add
 * @param params the layout parameters to set on the child
 */
public void addView(View child, LayoutParams params) {
    addView(child, -1, params);
}


/**
 * Adds a child view with the specified layout parameters.
 *
 * <p><strong>Note:</strong> do not invoke this method from
 * {
   @link #draw(android.graphics.Canvas)}, {
   @link #onDraw(android.graphics.Canvas)},
 * {
   @link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
 *
 * @param child the child view to add
 * @param index the position at which to add the child or -1 to add last
 * @param params the layout parameters to set on the child
 */
public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }

    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    // addViewInner() will call child.requestLayout() when setting the new LayoutParams
    // therefore, we call requestLayout() on ourselves before, so that the child's request
    // will be blocked at our level
    requestLayout();
    invalidate(true);
    addViewInner(child, index, params, false);
}

  看见addView 的方法中调运invalidate方法,这不就真相大白了。

   当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会讲通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调运invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。

最终会调用到ViewRootImpl的invalidate方法,从而调用 scheduleTraversals

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}

  

 进入到scheduleTraversals 放法中:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}


  来看看  mTraversalRunnable实现了什么,其代码如下:

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;
        }
    }
}


   从上面代码可以看出 , scheduleTraversals 通过Handler的Runnable发送一个异步消息,然后调用doTraversal方法,然后最终调用performTraversals()执行重绘。文章开头背景知识介绍说过的,performTraversals就是整个View树开始绘制的调用入口,所以说View调运invalidate方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals方法,从而执行performTraversals 进行重绘;

至此,对于为什么View的绘制入口是在performTraversals 本文分析完成;


二、  View绘制三大过程的具体分析


接下来,来具体分析下View绘制的三大过程,即performTraversals 做了具体什么事情,其源码如下:



private void performTravelsals(){
....
 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height)
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

...
 int desiredWindowWidth;
 int desiredWindowHeight;
 performLayout(lp, desiredWindowWidth, desiredWindowHeight);

...

 performDraw();



}

从上述源码分析,也验证来了文章开头分析的View 绘制过程的三大步的正确性;


2.1  Measure 过程分析

先来看看performMeasure  做了什么事情,首先看代码中,该函数会去调用 View .measure 函数;

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

measure 具体实现如下:


/**
 * <p>
 * This is called to find out how big a view should be. The parent
 * supplies constraint information in the width and height parameters.
 * </p>
 *
 * <p>
 * The actual measurement work of a view is performed in
 * {
   @link #onMeasure(int, int)}, called by this method. Therefore, only
 * {
   @link #onMeasure(int, int)} can and must be overridden by subclasses.
 * </p>
 *
 *
 * @param widthMeasureSpec Horizontal space requirements as imposed by the
 *        parent
 * @param heightMeasureSpec Vertical space requirements as imposed by the
 *        parent
 *
 * @see #onMeasure(int, int)
 */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }

    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

        resolveRtlPropertiesIfNeeded();

        int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                mMeasureCa
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android的视图绘制Android应用程序中的重要部分,它涉及到将用户界面元素绘制到屏幕上。以下是Android视图绘制的基本流程: 1. 触发绘制:当应用程序启动、布局发生变化或者手动调用 `invalidate()` 方法时,会触发视图绘制。 2. 测量布局:在绘制之前,Android会测量每个视图的大小。这个过程称为“测量布局”。测量布局是为了确定每个视图在屏幕上的位置和大小。 3. 布局:一旦测量完成,Android会根据视图的测量结果进行布局,确定每个视图在屏幕上的位置。 4. 绘制:布局完成后,Android会调用每个视图的 `draw()` 方法进行绘制。在 `draw()` 方法中,视图会绘制自己的内容,包括背景、文字、图片等。 5. 绘制层次:视图的绘制按照层次结构进行,即从父视图到子视图的顺序。父视图会先绘制自己,然后再绘制子视图。 6. 递归绘制:当父视图绘制完成后,它会递归地调用子视图的 `draw()` 方法,依次完成整个视图树的绘制过程。 7. 绘制缓存:为了提高绘制性能,Android使用了绘制缓存。绘制缓存可以将视图的绘制结果保存起来,在下次绘制时直接使用缓存,而不需要重新执行绘制操作。 总结来说,Android的视图绘制过程包括测量布局、布局、绘制绘制缓存。通过这个过程,Android应用程序可以将用户界面元素绘制到屏幕上,实现丰富多样的交互效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我心飞翔.坚定不移

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值