ViewTreeObserver源码学习

来源:互联网 发布:人工智能技术 知乎 编辑:程序博客网 时间:2024/05/22 23:45

一、理解ViewTreeObserver概念

ViewTreeObserver用来注册监听器,在视图树全局发生变化时收到通知。它不能被应用实例化,因为它是由视图提供,通过android.view.View#getViewTreeObserver()来获取。

ViewTree:视图树。在Android中,所有视图由View和View的子类组成。ViewGroup也是view的子类,它是View的容器,它可以装载View和ViewGroup。这样ViewGroup和View以树形结构一层一层的嵌套组合,就形成了视图树。

Observer:观察者。使用了观察者的设计模式,在这里,即ViewTree时被观察者,ViewTreeObserver是观察者,通过ViewTreeObserver注册监听来观察ViewTree的变化,当ViewTree发生变化,就会调用ViewTreeObserver的相关方法来通知其这一改变。我们可以在ViewTreeObserver中add自己的监听器,从而得到ViewTree的某一变化的通知做出自己的逻辑处理。

二、如何获取ViewTreeObserver

ViewTreeObserver是不能被应用程序实例化的,因为它是由视图提供的,通过view.getViewTreeObserver()获取。

//View.java

public ViewTreeObserver getViewTreeObserver() {    if (mAttachInfo != null) {        return mAttachInfo.mTreeObserver;    }    if (mFloatingTreeObserver == null) {        mFloatingTreeObserver = new ViewTreeObserver();    }    return mFloatingTreeObserver;}

其中,AttachInfo是View的一个内部类。当一个View附着到它的父Window中时,这个View能获取到一组View和父Window之间的信息,就存储在AttachInfo当中。AttachInfo中就有一个ViewTreeObserver对象。当mAttachInfo为空时,返回mFloatingTreeObserver,一个特殊的ViewTreeObserver。

那么,每一个view的mAttachInfo是怎么获取到的呢?AttachInfo是在View第一次attach到Window时,ViewRoot传给自己的子View的,然后沿着视图树,AttachInfo会一直传递到每一个View。

//ViewGroup.java

@Overridevoid dispatchAttachedToWindow(AttachInfo info, int visibility) {      ...    final int count = mChildrenCount;    final View[] children = mChildren;    for (int i = 0; i < count; i++) {        final View child = children[i];        child.dispatchAttachedToWindow(info,                combineVisibility(visibility, child.getVisibility()));    }    ...}

//View.java

void dispatchAttachedToWindow(AttachInfo info, int visibility) {    //System.out.println("Attached! " + this);    mAttachInfo = info;    ...}

另外,当新的View加入到ViewGroup中时,也会将AttachInfo传入。

//ViewGroup.java

private void addViewInner(View child, int index, LayoutParams params,        boolean preventRequestLayout) {    ...    AttachInfo ai = mAttachInfo;    if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {        boolean lastKeepOn = ai.mKeepScreenOn;        ai.mKeepScreenOn = false;        child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));        if (ai.mKeepScreenOn) {            needGlobalAttributesUpdate(true);        }        ai.mKeepScreenOn = lastKeepOn;    }    ...}

三、ViewTreeObserver的可用性

通过View.getViewTreeObserver()获得的ViewTreeObserver并不能保证在View的整个生命周期中一直是存活的。所以如果我们调用这个方法的时候,要长期引用这个ViewTreeObserver的话,在使用ViewTreeObserver之前就需要通过isAlive()方法去检查它是否是可用的。

//ViewTreeObserver.java

public boolean isAlive() {    return mAlive;}

其实我们在调用ViewTreeObserver的addOnXxxListener和removeOnXxxListener时,这些方法内部执行的一个操作就是checkIsAlive()去判断ViewTreeObserver是否可用。当ViewTreeObserver不可用时,将抛出IllegalStateException异常,并提示重新通过getViewTreeObserver()获取ViewTreeObserver。

//ViewTreeObserver.java

private void checkIsAlive() {    if (!mAlive) {        throw new IllegalStateException("This ViewTreeObserver is not alive, call "                + "getViewTreeObserver() again");    }}

四、走入ViewTreeObserver类内部

前面提到,ViewTreeObserver用来注册监听器,在视图树全局发生变化时收到通知。目前ViewTreeObserver可以监听11种全局事件,对应了11个内部类,以下8个是公开的。

 /** * 当视图树attached/detached到window时,回调的接口类定义 */public interface OnWindowAttachListener /** * 当window的焦点状态发生变化时,回调的接口类定义 */public interface OnWindowFocusChangeListener /** * 当视图树的焦点状态发生变化时,回调的接口类定义 */public interface OnGlobalFocusChangeListener /** * 当视图树的全局布局状态发生变化或者视图树中某个view的可见状态发生变化时,回调的接口类定义 */public interface OnGlobalLayoutListener /** * 当视图树将要绘制时,回调的接口类定义 */public interface OnPreDrawListener /** * 当视图树绘制时,回调的接口类定义 */public interface OnDrawListener/** * 当触摸模式发生变化时,回调的接口类定义 */public interface OnTouchModeChangeListener/** * 当视图树中某些组件发生滚动时,回调的接口类定义 */public interface OnScrollChangedListener

对于以上每一种接口,又对应:

  1. 对象集合:mOnXxxListeners
  2. 增加监听: addOnXxxListener(OnXxxListener listener)
  3. 移出监听: removeOnXxxListener(OnXxxListener victim)
  4. 分发事件: dispatchOnXxx()

五、ViewTreeObserver使用示例

接下来我们具体来看一个使用流程。比如,我们要在onCreate中测量一个控件的宽高。

以下是一种通过OnPreDrawListener来计算图片宽高的实现。通过imageView.getViewTreeObserver()获取ViewTreeObserver,然后增加一个自定义的ViewTreeObserver.OnPreDrawListener,覆写onPreDraw()方法。

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    final ImageView imageView = (ImageView) findViewById(R.id.imageview);    imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {                public boolean onPreDraw() {                    int height = imageView.getMeasuredHeight();                    int width = imageView.getMeasuredWidth();                    imageView.getViewTreeObserver().removeOnPreDrawListener(this);                    return true;                }            });}

首先看看addOnPreDrawListener方法。第一步,检测ViewTreeObserver是否可用,不可用的话抛出异常提示再获取一次。可用的话,则将这个listener添加到mOnPreDrawListeners(所有OnPreDrawListener对象的集合)当中。

//ViewTreeObserver.java

public void addOnPreDrawListener(OnPreDrawListener listener) {    checkIsAlive();    if (mOnPreDrawListeners == null) {        mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();    }    mOnPreDrawListeners.add(listener);}

为了防止多次收到通知调用onPreDraw,就需要在合适的时候移除这个监听。同样,也是先检测ViewTreeObserver是否可用,不可用的话抛出异常提示再获取一次。可用的话,则将这个listener从mOnPreDrawListeners删除掉。

//ViewTreeObserver.java

public void removeOnPreDrawListener(OnPreDrawListener victim) {    checkIsAlive();    if (mOnPreDrawListeners == null) {        return;    }    mOnPreDrawListeners.remove(victim);}

使用方法就此完成。在内部,ViewTreeObserver还需要负责分发事件通知给相应的监听对象,由dispatchOnXxx方法来执行。dispatchOnXxx方法一般由系统来调用。可以看到在dispatchOnPreDraw方法中,会去遍历mOnPreDrawListeners,然后逐个调用OnPreDrawListener.onPreDraw方法。

//ViewTreeObserver.java

public final boolean dispatchOnPreDraw() {    boolean cancelDraw = false;    final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;    if (listeners != null && listeners.size() > 0) {        CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();        try {            int count = access.size();            for (int i = 0; i < count; i++) {                cancelDraw |= !(access.get(i).onPreDraw());            }        } finally {            listeners.end();        }    }    return cancelDraw;}

其他几个OnXXXListener使用及原理基本同上。

六、View的绘制过程中dispatchOnXxx方法的调用时机

在实际应用中,虽然知道几个监听事件的大致概念,但怎么使用其实还是很迷惑的。也就是说,什么时候能触发这些事件然后通知ViewTreeObserver呢?即dispatchOnXxx系列方法系统到底是什么时候调用的呢?只有彻底明白了dispatchOnXxx()执行时机,才能更清楚地使用ViewTreeObserver。

我们知道view的真正绘制流程从ViewRootImpl类中的performTraversals()开始。我只截取相关部分代码说明。

//ViewRootImpl.java

// 第一段代码:    final View host = mView; //DecorView根布局    ......    final View.AttachInfo attachInfo = mAttachInfo;    ......    if (mFirst) { //表示第一次被请求执行测量、布局和绘制        //计算Activity窗口的宽高        ......        // 通知视图树已经绑定到它的父窗口上        ......        host.dispatchAttachedToWindow(attachInfo, 0);        attachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);        ......    } else {        ......    }

可以看到,如果Activity窗口第一次执行测量、布局和绘制操作,计算完窗口的宽和高之后,就会给mAttachInfo设置一些activity的属性信息,然后通过attachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true)分发通知视图树已经绑定到父窗口。

接下来看第二段代码:

// 第二段代码:boolean layoutRequested = mLayoutRequested && !mStopped;    if (layoutRequested) {        final Resources res = mView.getContext().getResources();        if (mFirst) {            // mInTouchMode表示View所处的Window是否处于触摸模式            mAttachInfo.mInTouchMode = !mAddedTouchMode;            // 确保这个Window的触摸模式已经被设置            ensureTouchModeLocally(mAddedTouchMode);        } else {        ......        }

进入到ensureTouchModeLocally方法内部,可以看到如果触摸模式发生变化,则会mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged通知出发模式变化的通知。

private boolean ensureTouchModeLocally(boolean inTouchMode) {    if (mAttachInfo.mInTouchMode == inTouchMode) return false;    mAttachInfo.mInTouchMode = inTouchMode;    mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);    return (inTouchMode) ? enterTouchMode() : leaveTouchMode();}

接下来看第三段代码。

开始执行measure过程。

//第三段代码if (!mStopped) { //此窗口的拥有者Activity是否处于停止状态            //此窗口当前获得焦点的控件是否发生变化            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()                 ......                // 开始执行测量操作                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                ......                                   if (measureAgain) { //是否需要重新测量                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                }                layoutRequested = true;            }        }    } else {        ......    }

第四段代码:执行layout过程。

在layout过程执行结束,则会通过attachInfo.mTreeObserver.dispatchOnGlobalLayout()通知全局布局变化事件。

//第四段代码final boolean didLayout = layoutRequested && !mStopped;    boolean triggerGlobalLayoutListener = didLayout            || attachInfo.mRecomputeGlobalAttributes;    if (didLayout) {        performLayout(lp, desiredWindowWidth, desiredWindowHeight);        ......    }     ......    if (triggerGlobalLayoutListener) {         attachInfo.mRecomputeGlobalAttributes = false;        attachInfo.mTreeObserver.dispatchOnGlobalLayout();    }

第五段代码:执行draw过程。

首先,会通过attachInfo.mTreeObserver.dispatchOnPreDraw()通知观察者绘制过程开始了。如果某一个观察者OnPreDrawListener.onPreDraw返回true,则绘制过程会被取消掉或者重新开始调度。

//第五段代码boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||            viewVisibility != View.VISIBLE;  //是否取消绘制    if (!cancelDraw && !newSurface) {        if (!skipDraw || mReportNextDraw) {            ......            performDraw();        }    } else {        if (viewVisibility == View.VISIBLE) {            // 重新开始            scheduleTraversals();        } ......    }

在performDraw()->draw()执行过程中,又会涉及到两个事件的分发。如果有发生滚动的话,则会通过attachInfo.mTreeObserver.dispatchOnScrollChanged()通知滚动事件;另外,通过attachInfo.mTreeObserver.dispatchOnDraw()通知观察者绘制的执行。

//ViewRootImpl.java

private void draw(boolean fullRedrawNeeded) {    ......    final AttachInfo attachInfo = mAttachInfo;    if (attachInfo.mViewScrollChanged) {        attachInfo.mViewScrollChanged = false;        attachInfo.mTreeObserver.dispatchOnScrollChanged();    }    ......    attachInfo.mTreeObserver.dispatchOnDraw();    ......}

到这里,绘制过程与ViewTreeObserver.dispatchOnXxx方法的调用关系大致就清晰了。还有几个事件这里就不一一说明啦。

七、总结

关于ViewTreeObserver的源码学习就大致就这样,谈了谈基本概念、如何用、还有底层的一些分发机制等。还有很多细节还需要不断深入了解,本文不对之处欢迎大家多多讨论。

原创粉丝点击