View绘制流程——第四篇

ViewRootImpl

Measure

Layout

Draw

入口函数:performTraversals

Android 28源码:

performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)

Measure

MeasureSpec: 高2位(mode)低30位(size)

onMeasure和measure的区别

Measure不可以被重写 负责进行测量的传递

onMeasure可以被重写,负责测量的具体实现

// Implementation of weights from WindowManager.LayoutParams

// We just grow the dimensions as needed and re-measure if

// needs be

在这种情况下,performMeasure会调用两次

测量结束之后,开始进行布局

Layout

performLayout(lp, mWidth, mHeight);

mView.layout(0,0,mView,getMeasuredWidth(),mView.getMeasuredHeight)

特殊情况:如果在layout的过程中,还有其他的layout请求过来,那么就需要清除所有的标志位然后做一整套的请求,测量,布局去处理这个情况

View.layout->View.onLayout(changed, l ,t , r ,b)

View 当中的onLayout是一个空实现,可以提供给子类去重写

View的layout与measure不同,它是一个public的方法且不是final,可以由子类继承去重写

那我们看下ViewGroup的实现

在ViewGroup当中,layout是被final修饰,且也继承了View的这个方法的实现,也就是说,我们通过实现onLayout方法就可以指定我们自己的类的布局方式,因为layout中会调用到onLayout函数

在ViewGroup当中,onLayout被abstract修饰,证明ViewGroup的子类一定要去自己实现onLayout来进行具体的布局

对于View的子类和ViewGroup的子类我们分别来看一个具体的例子:

1.TextView

在TextView当中没有去重写layout,只是简单的重写了onLayout方法

2.RelativeLayout

ViewGroup的子类没办法重写layout函数,在onLayout中,RelativeLayout将子View通过循环遍历的方式取出,然后去让子View调用自己的layout方式去进行布局

onLayout的职责:它并不负责自己的布局,它的布局都是由它的爸爸去出发,它只负责自己子view 的布局调用

Draw

performDraw();

boolean canUseAsync = draw(fullRedrawNeeded);

drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingRequired, dirty, surfaceInsets)

canvas = mSurface.lockCanvas(dirty);

mView.draw(canvas)

onDraw(cavas){空实现,对自己进行绘制,需要子类自己去实现}

dispatchDraw(canvas);{空实现,对孩子进行绘制,需要子类自己去实现}

我们分别从View 和 ViewGroup的角度看下这相关的三个函数

Draw:传递数据

onDraw:绘制自身

dispatchDraw:绘制子View

View:

Draw 是public且不是fianl类型,子类可以继承去进行重写

onDraw是空实现,子类可以重写也可以不重写

dispatchDraw是空实现,子类可以重写也可以不重写

ViewGroup:

没有重写draw以及onDraw方法

重写了dispatchDraw方法

drawChild(canvas, child, drawingTime);

child.draw(canvas, this, drawingTime);

在ViewGroup中没有对三个函数做特殊限制也就是说,任何ViewGroup的子类都可以重写三个函数,或者直接使用这三个函数

RelativeLayout:

没有重写父类的三个方法

TextView:

重写了View的onDraw方法

requestLayout 和 invalidate 详解

View.requestLayout:

官方解释:

当视图的布局有改变的时候,刷新

它将会在视图树上遍历执行一边layout

这个不应该在视图层级正在layout的时候调用

如果layout操作正在执行,这个请求会被接受在当前的layout操作结束的时候,layout会被再次调用

或者在当前的帧被绘制完成然后下一次的layout操作发生的时候

子类如果想要重写这个方法的话,应该先调用父类的函数实现去正确的处理可能会发生的一些 布局期间请求的 错误。

1.如果测量缓存不为空,将测量缓存清空??

2.如果当前的根布局在布局过程中,return函数

3.如果爸爸不为空,而且爸爸已经布局结束,那么就调用爸爸的requestLayout

ViewGroup没有重写View的requestLayout方法。

重点:

这里有两个标志位的设置

mPrivateFlags |= PFLAG_FORCE_LAYOUT;

mPrivateFlags |= PFLAG_INVALIDATED;

也就是说这个是从子节点往根节点的逆序遍历调用过程

最终调用到的就是DecorView ,那么我们看下DecorView的爸爸是谁

view.assignParent(this);

那么就是说,DecorView的爸爸就是ViewRootImpl

ViewRootImpl.requestLayout

如果没有在处理layout请求的过程中

1.检查是不是主线程

2.将layout请求的flag设置为true

3.调用scheduleTraversals

1.mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {

@Override

public void run() {

doTraversal();

performTraversals();

又重新调回了performTrarsals,重新开始了 measure/layout/draw整个流程

那么是不是所有的子View都需要测量呢??这时候标志位就派上用场了

final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

if (forceLayout || needsLayout) {

// first clears the measured dimension flag

mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

// measure ourselves, this should set the measured dimension flag back

onMeasure(widthMeasureSpec, heightMeasureSpec);

所以只有调用了requestlayout的View才会进行重新测量

在View.layout中,在调用了onLayout之后有一个这样的代码

mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;

标志位已经恢复

在进行完layout之后,标志位复原

在draw函数中,

final int privateFlags = mPrivateFlags;

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&

(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

重点看这个dirtyOpaque

if (!dirtyOpaque) {

drawBackground(canvas);

}

if (!dirtyOpaque) onDraw(canvas);

dirtyOpaque == TRUE的时候,不会调用自身的绘制方法,子View也雷同

所以requestLayout

1.由子View调用爸爸的requestLayout,一直到ViewRootImpl为止,它的requestLayout又调用到了performTraversals函数,开始了 measure,layout,draw由于在layout的时候,标志位恢复,导致draw就不会被执行,也就是说requestLayout就到layout函数为止就结束了。

invalidate()

官方解释:

重新绘制全部的View,如果这个View是可见的,onDraw就会在未来的某个点被调用,这个函数一定要从UI线程调用,如果在非UI线程调用的话,调用postInvalidate方法

invalidate(true);

public void invalidate(boolean invalidateCache) {

官方解释:

这个函数是invalidate实际开始进行工作的地方一个完整的invalidate函数调用造成视图缓存被重绘,但是这个方法可以通过高设置

invalidateCache参数为false跳过这些重绘的步骤对于那些不需要的情况,例如一个组件的内容和大小都没变化

invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,

boolean fullInvalidate) {

1.如果需要跳过重绘,跳过

return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&

(!(mParent instanceof ViewGroup) ||

!((ViewGroup) mParent).isViewTransitioning(this));

2.p.invalidateChild(this, damage);

ViewGroup.invalidateChild(@Deprecated) instead of onDescendantInvalidated

// HW accelerated fast path

onDescendantInvalidated(child, child);

if (mParent != null) {

mParent.onDescendantInvalidated(this, target);

}

So,这个方法也最后会调用到最后一个爸爸的onDescendantInvalidated这个方法,那我们直接到ViewRootImpl去看下

@Override

public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {

if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {

mIsAnimating = true;

}

invalidate();

}

void invalidate() {

mDirty.set(0, 0, mWidth, mHeight);

if (!mWillDrawSoon) {

scheduleTraversals();

}

}

Ok,看到这里优势很熟悉的代码啦,

scheduleTraversals这个函数最后仍然是调用到了performTraversals,请求重绘View的树,即draw过程,假如视图没有发生大小的变化的话就不回进行layout的过程,只是绘制需要绘制的那些视图

mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;

是否执行onMeasure方法也和这个标志位有关系mPrivateFlags

是否执行onLayout也和这个标志位有关系mPrivateFlags

所以mPrivateFlags的变化是控制整个绘制流程的一个重点!!!!!!!!!

1 invalidate,请求重新draw,只会绘制调用者本身。

2 setSelected 也是请求invalidate,同上

*/

public void setSelected(boolean selected) {

//noinspection DoubleNegation

invalidate(true);

}

}

3.setVisibility

当View从INVISIBLE变为VISIBILE,会间接调用invalidate方法,继而绘制该View,而从INVISIBLE/VISIBLE变为GONE之后,由于View树的大小发生了变化,会进行measure/layout/draw,同样,他只会绘制需要重绘的视图。

if (newVisibility == VISIBLE) {

invalidate(true);

}

/* Check if the GONE bit has changed */

if ((changed & GONE) != 0) {

requestLayout();

((View) mParent).invalidate(true);

}

if ((changed & INVISIBLE) != 0) {

/*

  • If this view is becoming invisible, set the DRAWN flag so that

  • the next invalidate() will not be skipped.

*/

mPrivateFlags |= PFLAG_DRAWN;

}

  • setEnable:请求重新draw,只会绘制调用者本身。invalidate(true);

  • requestFocus:请求重新draw,只会绘制需要重绘的视图。

补充学习:

1.http://blog.csdn.net/yanbober/article/details/46128379/

2.http://blog.csdn.net/a553181867/article/details/51583060

View中 getWidth 和 getMeasuredWidth 的区别

先看下getWidth:

/**

    • Return the width of your view.*

*** @return The width of your view, in pixels.

  • /

@ViewDebug.ExportedProperty(category = "layout")

public final int getWidth() {

return mRight - mLeft;

}

返回你视图的宽度,以像素为单位

这些坐标值是从onLayout中传递过来

有他的右边的坐标减去它的左边的坐标得出

getMeasuredWidth

public final int getMeasuredWidth() {

return mMeasuredWidth & MEASURED_SIZE_MASK;

}测量阶段中计算出的宽度。

返回未经加工的测量的宽度

源头是从onMeasure传递进来

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {

mMeasuredWidth = measuredWidth;

mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;

}

  • getMeasuredWidth是measure阶段获得的View的原始宽度。
  • getWidth是layout阶段完成后,其在父容器中所占的最终宽度

举一个例子

我们自定义一个lineaerlayout

不复写它的onMeasure方法,那么这里的测量结果一定是我们xml设置的值

在layout函数中我们在传入坐标的地方做一些手脚,那么最终显示是以getWidth的数据为准

child.layout(child.getLeft() ,child.getTop(), child.getRight() + 400, child.getBottom());

你可能感兴趣的:(View绘制流程——第四篇)