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());

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容