欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > 内容正文

View机制深入学习(二)——View树的遍历,view深入学习

来源: 开发者 投稿于  被查看 45289 次 评论:250

View机制深入学习(二)——View树的遍历,view深入学习


一、遍历View树的入口是ViewRootImpl的scheduleTraversal函数

/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/
void scheduleTraversals() {
    if (!mTraversalScheduled) { // 判断当前是否已经在做遍历
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().postSyncBarrier();

        // 这里是“Project Butter”的产物,一旦有VSYNC信号来临,mTraversalRunnable就会被调用
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
    }
}

1、mTraversalRunnable:

/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
        try {
            // 最终实现View树遍历的函数
            performTraversals();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
}

可以看到遍历View树最终的入口函数为调用ViewRootImpl的performTraversals函数;


2、ViewRootImpl#performTraversals

/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/
private void performTraversals() {
    // 记录ViewRootImpl管理的View树的根节点,final修饰,避免运行过程中修改
    final View host = mView;
    /** View树遍历的主要三个步骤measure、layout、draw **/
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ......
    performDraw();
}

其实这三个函数仅是做了一层简单封装:

/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
    // 进而调用View的measure函数
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
 
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    final View host = mView;
 
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ......
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}
 
private void performDraw() {
    ......
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ......
}


则上述的函数调用过程可以描述为:

/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
draw(fullRedrawNeeded);

即总共分为三个主要过程:measure过程,layout过程,draw过程;


******************************************measure过程****************************************** 二、measure过程 measure即测量视图的大小

/** \frameworks\base\core\java\android\view\View.java **/
// @params 用于确定视图的宽度和高度的规格和大小
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
}
    可以看到最终是调用onMeasure来实现;而传入的参数:widthMeasureSpec,heightMeasureSpec;是用来确定视图的宽度与高度的规格和大小;

1、MeasureSpec     每一个spec(int类型)的格式:mode(前两位)+size;     MeasureSpec由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。通过MeasureSpec.specSize和MeasureSpec.specMode可以分别获取其Size与Mode值;

/** \frameworks\base\core\java\android\view\View.java **/
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    private static final int UNSPECIFIED = 0 << MODE_SHIFT;
    private static final int EXACTLY     = 1 << MODE_SHIFT;
    private static final int AT_MOST     = 2 << MODE_SHIFT;
 
 
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
 
    public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); }
 
    public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK);}
 
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        ......
        return makeMeasureSpec(size, mode);
    }
 
    public static String toString(int measureSpec) { ...... }
}

其中specMode一共有三种类型,如下所示:

1. EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

2. AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

3. UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

很明显,MATCH_PARENT对应EXACTTLY,WRAP_CONTENT对应AT_MOST;
2、View#onMeasure:

/** \frameworks\base\core\java\android\view\View.java **/
// 这里仅是View提供的默认的onMeasure实现,具体的View需要根据自身情况进行重写
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
 
// AT_MOST、AT_MOST情况返回measureSpec对应的specSize,否则返回getSuggestedMinimumHeight等
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
 
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.AT_MOST:
        result = specSize;
        break;
    }
    return result;
}
/** 计算并设置视图界面大小 **/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;
 
        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
 
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
 
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。

这里仅是View提供的onMeasure的默认实现,对于扩展的视图,如FrameLayout等需要考虑自身的因素进行测量; 以ViewGroup为例具体来看其重写的onMeasure方法。
3、FrameLayout#onMeasure

/** \frameworks\base\core\java\android\widget\FrameLayout.java **/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount(); // 由于ViewGroup是多个View的组合,获取子View的个数
 
    /** 判断父对象的Mode请求 **/
    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    // @value final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1);
    mMatchParentChildren.clear();
 
    int maxHeight = 0;  // 所有子对象中测量到的最大高度
    int maxWidth = 0;   // 所有子对象中测量到的最大宽度
    int childState = 0;
 
    for (int i = 0; i < count; i++) { // 循环处理所有子对象
        final View child = getChildAt(i); // ViewGroup中的方法,获取子View
        if (mMeasureAllChildren || child.getVisibility() != GONE) { // 当可见性为GONE时,不需要测量
        // 递归调用此函数,对子View进行measure过程
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            /***measure过程,这里用以获取视图的最大高度与宽度 **/
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) {
                  // 可以看到MATCH_PARENT的View只能显示一个
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
 
    // ViewGroup本身的Padding要计算在内
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
 
    // 计算适应当前View的最小高度值
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
 
    // 将背景图片的大小也要计算在内
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }
 
    /** 设置测量好的结果 **/
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    ......
}
    可以看到重写的onMeasure方法流程也大致相同,都是综合考虑父控件的尺寸大小,以及Padding,Margin等得到视图的最大Size,通过setMeasureDeimenson最后保存设置计算出来的结果。

    由于ViewGroup可能包含很多子View,遍历View树通过measureChildWithMargins来执行;通过下面的函数可以看到,其会继续调用子View的measure函数,开始对子View的新一轮measure过程,直至将所有树的节点都遍历一遍。

3.1、ViewGroup#measureChildWithMargins:

/** \frameworks\base\core\java\android\view\ViewGroup.java **/
// 这里需要综合考虑Padding与Margins
protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
   
    // 测量子View的MeasureSpce值
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
            + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
            + heightUsed, lp.height);
    // 递归调用子View的measure函数;如果子View是ViewGroup,则会依然按照前面的步骤再递归调用其子View的measure一直遍历下去
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
 
/** 可以看到measureChildWithMargins就是 measureChildren +measureChild函数,加上考虑Margins的版本 **/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
 
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
 
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
 
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
    Measure过程:由上面可见视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。

    因此我们在自定义View时可以重写onMeasure函数,通过setMeasureDeimenson设置最终的视图大小。


******************************************Layout过程******************************************
三、layout过程     通过执行performMeasure后,View Tree中各个View的大小基本确定下来,接下来需要进行另一个遍历,进行位置测量。 接下来执行host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

/** \frameworks\base\core\java\android\widget\View.java **/
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    // 同样形式调用onLayout来实现
        onLayout(changed, l, t, r, b);
        ......
    }
}

    l,t,r,b分别表示该View对象与父对象的左上右下边框的距离。

1、View#onLayout:

/** \frameworks\base\core\java\android\view\View.java **/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

    不像View.onMeasure提供了一个默认的实现,这里的onLayout直接是一个空函数,任何继承自View的类都需要自己Override这个函数来进行布局。 而对于ViewGroup:

/** \frameworks\base\core\java\android\view\ViewGroup.java **/
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

其onLayout函数则是一个abstract函数,强制要求其子类必须Override。 同样以FrameLayout为例看其具体重写的方法。
2、FrameLayout#onLayout:

/** \frameworks\base\core\java\android\widget\FrameLayout.java **/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false/* no force left gravity */);
}
 
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount(); // 获取子对象个数
 
    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();
 
    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();
 
    mForegroundBoundsChanged = true;
   
    for (int i = 0; i < count; i++) { // 同样遍历所有子对象
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
 
            int childLeft;
            int childTop;
 
            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }
 
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
 
            // 根据对应的Layout中设置Gravity属性进行布局
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }
 
            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
 
            // 递归调用子View的layout过程
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
Layout过程也是一个自View树根从上往下遍历进行layout布局的过程,需要根据具体的Layout以及Gravity等属性完成子View在父View中的布局。


******************************************Draw过程******************************************
四、draw过程:     performDraw调用,来在“画板”上产生UI数据,并在适当的时机与SurfaceFlinger进行整合,最终显示到屏幕上。

/** \frameworks\base\core\java\android\view\View.java **/
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    /*** 可以看到绘制过程主要分为了如下的六步 **/
    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */
 
    // Step 1, 如果需要的话,绘制背景
    int saveCount;
 
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
 
    // 对于一些常见情况,可以跳过Step2与Step5直接进行绘制
    finalint viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
 
        // Step 4, draw the children
        dispatchDraw(canvas);
 
        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
 
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
        // we're done...
        return;
    }
 
    /** 在一些对速度要求不高的情况(不常见),使用完整的绘制过程*/
}
 
/** 可以看到这两个重要的函数,并未给出具体的实现 **/
/** 重写该函数即可绘制自己自定义的View
 * Implement this to do your drawing.
 */
protected void onDraw(Canvas canvas) {}
 
/** 重写该函数用以绘制子View
 * Called by draw to draw the child views. This may be overridden
 * by derived classes to gain control just before its children are drawn
 * (but after its own view has been drawn).
 * @param canvas the canvas on which to draw the view
 */
 protected void dispatchDraw(Canvas canvas) {}
}


五、View树遍历的时机     前面涉及的View树的遍历,共经历Measure、Layout、Draw过程,而在什么情况下会触发View树: 1、View.requestLayout:     View对象可以通过调用requestLayout来主动申请遍历; 1)View#requestLayout:

/** \frameworks\base\core\java\android\view\View.java **/
public void requestLayout() {
    ......
 
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
 
    if (mParent != null && !mParent.isLayoutRequested()) {
    // @value protected ViewParent mParent;
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}
可以看到当需要进行Layout时,会主动调用ViewParent(ViewParent仅是一个Interface,其具体运行时类型为ViewRootImpl)的requestLayout函数:

/**\frameworks\base\core\java\android\view\ViewRootImpl **/
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
    // 该函数用来判断当前线程是否是主线程,因为只有主线程才能操作UI对象
        checkThread();
        mLayoutRequested = true;
        // 可以看到这里调用了前面提到的View树的遍历入口函数
        scheduleTraversals();
    }
}
可以看到这里最终调用上面提到的ViewTree遍历的入口函数scheduleTraversals函数开始View树的遍历。


2、View.setLayoutParams: 当View的布局发生变化时,它会主动申请一次遍历

/** \frameworks\base\core\java\android\view\View.java **/
public void setLayoutParams(ViewGroup.LayoutParams params) {
    if (params == null) {
        throw new NullPointerException("Layout parameters cannot be null");
    }
    mLayoutParams = params;
    resolveLayoutParams();
    if (mParent instanceof ViewGroup) {
        ((ViewGroup) mParent).onSetLayoutParams(this, params);
    }
    // 这里申请调用requestLayout来遍历View树
    requestLayout();
}
最终调用上面的requestLayout来遍历View树。


3、View.requestFocus:

请求View树的draw()过程,但只绘制“需要重绘”的视图。

/** \frameworks\base\core\java\android\view\View.java **/
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
    return requestFocusNoSearch(direction, previouslyFocusedRect);
}
 
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
    if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
            (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
        return false;
    }
    ......
 
    handleFocusGainInternal(direction, previouslyFocusedRect);
    return true;
}
 
void handleFocusGainInternal(@FocusRealDirectionint direction, Rect previouslyFocusedRect) { 
    if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
        mPrivateFlags |= PFLAG_FOCUSED; 
        View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
        if (mParent != null) {
        // 遍历子View
            mParent.requestChildFocus(this, this);
        }
        if (mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
        } 
        // 看下这个函数
        onFocusChanged(true, direction, previouslyFocusedRect);
        refreshDrawableState();
    }
}
 
protected void onFocusChanged(boolean gainFocus, @FocusDirectionint direction,
        @Nullable Rect previouslyFocusedRect) {
    .....
    invalidate(true);
    .....
}
可以看到最终调用下面情况invalidate来进行重绘。


4、View.invalidate: invalidate意思是“使无效”,即将当前UI界面设定为无效,从而引起重绘过程;

    请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”

视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。

1)View#invalidate

/** \frameworks\base\core\java\android\view\View.java **/
public void invalidate() {
    invalidate(true);
}
 
void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
 
public void invalidate(int l, int t, int r, int b) {
    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}
 
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }
 
    if (skipInvalidate()) { // 用于判断是否忽略这个invalidate操作
        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)) {
    // 设置相应invalidate标志位
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN; // 设置此标志用以保证invalidate的执行
        }
        mPrivateFlags |= PFLAG_DIRTY; // 表示由相应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();
            }
        }
 
        // Damage the entire IsolatedZVolume receiving this view's shadow.
        if (isHardwareAccelerated() && getZ() != 0) {
            damageShadowReceiver();
        }
    }
}

2)ViewRootImpl#invalidateChildInParent:

/** \frameworks\base\core\java\android\view\ViewRootImpl.java **/
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    .....
    final Rect localDirty = mDirty;
    if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
        mAttachInfo.mSetIgnoreDirtyState = true;
        mAttachInfo.mIgnoreDirtyState = true;
    }
 
    localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
    final float appScale = mAttachInfo.mApplicationScale;
    final boolean intersected = localDirty.intersect(0, 0,
            (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    if (!intersected) {
        localDirty.setEmpty();
    }
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
    // 遍历View树入口函数
        scheduleTraversals();
    }
    return null;
}
一般引起invalidate()操作的函数如下:

    1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

    2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。

    3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。

    当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。

    同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。

    4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。


5、dispatchAppVisibility     当应用程序的可见性发生改变时(如启动一个新的Activity),会调用这个函数;     一旦ViewRootImpl受到Visibility改变的消息,就会组织一次遍历


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关频道:

用户评论