美文网首页
View绘制流程——第四篇

View绘制流程——第四篇

作者: 丸子哒哒哒 | 来源:发表于2019-05-11 14:11 被阅读0次

    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绘制流程——第四篇

          本文链接:https://www.haomeiwen.com/subject/svexaqtx.html