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
对于以上每一种接口,又对应:
- 对象集合:mOnXxxListeners
- 增加监听: addOnXxxListener(OnXxxListener listener)
- 移出监听: removeOnXxxListener(OnXxxListener victim)
- 分发事件: 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的源码学习就大致就这样,谈了谈基本概念、如何用、还有底层的一些分发机制等。还有很多细节还需要不断深入了解,本文不对之处欢迎大家多多讨论。
- ViewTreeObserver源码学习
- android ViewTreeObserver中文翻译学习
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver
- ViewTreeObserver?
- ViewTreeObserver
- ViewTreeObserver
- Android学习笔记(二)--- ViewTreeObserver
- ViewTreeObserver解释--Android学习笔记6-1
- 通过ViewTreeObserver获取View最终的大小及源码分析
- ViewTreeObserver.OnPreDrawListener
- 设计模式-观察者模式
- leetcode【第十六周】左叶子节点求和
- How to get the Projection ProjectionMatrixFromCameraIntrinsics
- 在android使用httpclient时出现“SocketException: Broken Pipe”的解决方法
- IntentService源码解析
- ViewTreeObserver源码学习
- Ubuntu下ns2中生成补丁和打补丁方法
- android https请求的使用
- 打印出漂亮的json use python command python -m json.tool
- Glide的使用,加载图片只要一句话
- mybatis日期查询
- SGI特殊空间配置器std::alloc
- Android 系统默认参数的修改
- [Codeforces]Hamburgers