监听视图树 ViewTreeObserver 获取View的宽高

时间:2022-05-16 21:04:38

前奏:在哪里可以获取到View的宽高

我们知道,在onCreate方法执行完毕以后,View才开始被测量,所以我们在onCreate方法里面通过view.getWidth()或view.getMeasuredWidth()得到的View的宽高肯定是0,因为它还没有被测量,所以在这个时候去获取它的宽高,肯定是不行的。另外经过测试发现,即使是在onResume中,View往往也还是没有被测量到的,那我们可能就郁闷了,难道我就不能在运行时动态获取View的宽高了吗?


当然是可以的。我们首先想到的方法就是,既然我不知道View什么时候开始被测量,那我就手动测量啦,比如对于一个WRAP_CONTENT的View,我们通过手动调用view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)便可实现主动测量View的宽高。这样在测量后我就可以立即拿到测量的大小了(注意仅仅是获取到view.getMeasuredWidth()的值,而仍无法获取view.getWidth()的值)。

但是这样做有一个隐患,那就是如果View的宽高不是WRAP_CONTENT而是具体的值或MATCH_PARENT,那测量值和实际值可能就是完全不一样的。另外,手动测量View后会导致整个View树重新测量,对性能也有一定的影响。这时OnGlobalLayoutListener就该上场了。

OnGlobalLayoutListener是ViewTreeObserver的内部类,这是一个注册监听视图树的观察者,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到,因此我们可以利用OnGlobalLayoutListener来获得一个视图的真实高度。ViewTreeObserver不能直接实例化,而是通过view.getViewTreeObserver()获得。
注意,由于布局状态可能会发生多次改变,因此OnGlobalLayoutListener的onGlobalLayout可能被回调多次,所以我们在 第一次获得值之后就将listener注销掉。 

官方文档简介


ViewTreeObserver
A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include, but are not limited to不限于, layout of the whole tree, beginning of the drawing pass, touch mode change.... A ViewTreeObserver should never be instantiated实例化 by applications as it is provided by the views hierarchy. Refer to getViewTreeObserver() for more information.

public ViewTreeObserver getViewTreeObserver ()
Returns the ViewTreeObserver for this view's hierarchy. The view tree observer can be used to get notifications when global events, like layout, happen. The returned ViewTreeObserver observer is not guaranteed to remain valid for the lifetime of this View. If the caller of this method keeps a long-lived reference to ViewTreeObserver, it should always check for the return value of isAlive().

内部接口

有部分接口被隐藏hide了
OnDrawListener:
  • 回调方法 public void onDraw()
  • Interface definition for a callback to be invoked when the view tree is about to将要 be drawn.
  • At this point, views cannot be modified in any way.
  • Unlike with OnPreDrawListener, this method cannot be used to cancel the current drawing pass.
  • An OnDrawListener listener cannot be added or removed from this method.

OnGlobalFocusChangeListener:当在一个视图树中的焦点状态发生改变时回调

  • 回调方法 public void onGlobalFocusChanged(View oldFocus, View newFocus);
  • Interface definition for a callback to be invoked when the focus state焦点状态 within the view tree changes.
  • When the view tree transitions from touch mode to non-touch mode, oldFocus is null.
  • When the view tree transitions from non-touch mode to touch mode, newFocus is null.
  • When focus changes in non-touch mode (without transition from or to touch mode) either oldFocus or newFocus can be null.

OnGlobalLayoutListener:当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时回调
  • 回调方法 public void onGlobalLayout();
  • Interface definition for a callback to be invoked when the global layout state布局状态 or the visibility of views within the view tree changes.

OnPreDrawListener:当一个视图树将要绘制时回调
  • 回调方法 public boolean onPreDraw(); Return true to proceed with the current drawing pass, or false to cancel.
  • Interface definition for a callback to be invoked when the view tree is about to将要 be drawn.
  • At this point, all views in the tree have been measured and given a frame.
  • Clients can use this to adjust适应 their scroll bounds or even to request a new layout before drawing occurs.

OnScrollChangedListener:当一个视图树中的一些组件发生滚动时回调
  • 回调方法 public void onScrollChanged();
  • Interface definition for a callback to be invoked when something in the view tree has been scrolled滚动.

OnTouchModeChangeListener:当一个视图树的触摸模式发生改变时回调
  • 回调方法 public void onTouchModeChanged(boolean isInTouchMode); True if the view hierarchy is now in touch mode, false  otherwise.
  • Interface definition for a callback to be invoked when the touch mode触摸方式 changes.

OnWindowAttachListener:
  • 回调方法 public void onWindowAttached();  和 public void onWindowDetached();
  • Interface definition for a callback to be invoked when the view hierarchy视图数 is attached to and detached from its window.

OnWindowFocusChangeListener:
  • 回调方法 public void onWindowFocusChanged(boolean hasFocus); Set to true if the window is gaining focus, false if it is losing focus.
  • Interface definition for a callback to be invoked when the view hierarchy's window focus state窗口焦点 changes.

公共方法

添加监听
  • void addOnDrawListener(ViewTreeObserver.OnDrawListener listener):Register a callback to be invoked when the view tree is about to be drawn.
  • void addOnGlobalFocusChangeListener(ViewTreeObserver.OnGlobalFocusChangeListener listener):Register a callback to be invoked when the focus state within the view tree changes.
  • void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener):Register a callback to be invoked when the global layout state or the visibility of views within the view tree changes
  • void addOnPreDrawListener(ViewTreeObserver.OnPreDrawListener listener):Register a callback to be invoked when the view tree is about to be drawn
  • void addOnScrollChangedListener(ViewTreeObserver.OnScrollChangedListener listener):Register a callback to be invoked when a view has been scrolled.
  • void addOnTouchModeChangeListener(ViewTreeObserver.OnTouchModeChangeListener listener):Register a callback to be invoked when the invoked when the touch mode changes.
  • void addOnWindowAttachListener(ViewTreeObserver.OnWindowAttachListener listener):Register a callback to be invoked when the view hierarchy is attached to a window.
  • void addOnWindowFocusChangeListener(ViewTreeObserver.OnWindowFocusChangeListener listener):Register a callback to be invoked when the window focus state within the view tree changes.
移除监听
注意在早期版本中,某个程序员起了一个奇葩的名字,后来被废除了
  • void removeGlobalOnLayoutListener(ViewTreeObserver.OnGlobalLayoutListener victim):This method was deprecated in API level 16. Use removeOnGlobalLayoutListener instead
  • void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener victim):Remove a previously installed global layout callback
其他方法
  • final void dispatchOnDraw():Notifies registered listeners that the drawing pass is about to start.
  • final void dispatchOnGlobalLayout():Notifies registered listeners that a global layout happened.
  • final boolean dispatchOnPreDraw():Notifies registered listeners that the drawing pass is about to start.
  • boolean isAlive():Indicates指示、判断 whether this ViewTreeObserver is alive是否可用.

案例:获取View的宽高

public class MainActivity extends Activity implements OnGlobalLayoutListener {
    private View view;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        view = findViewById(R.id.iv);
        Log.i("bqt", "onCreate中:" + view.getWidth() + "--" + view.getMeasuredWidth());//0--0。在onCreate中还没有被测量与布局,所以获取不到宽高。
        view.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }
    @Override
    public void onGlobalLayout() {// 当layout执行结束后回调
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);//使用完之后必须立刻撤销监听,否则会一直不停的测量,这比较耗性能
        Log.i("bqt", "onGlobalLayout中:" + view.getWidth() + "--" + view.getMeasuredWidth());//200-200。获取到的就是我们在XML中设置的值
    }
    @Override
    protected void onResume() {
        super.onResume();
        Log.i("bqt", "onResume中:" + view.getWidth() + "--" + view.getMeasuredWidth());//0-0。可以看到,即使是在onResume中,仍然获取不到任何值
        view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);//手动去测量,一般是用LayoutParams.WRAP_CONTENT去测量
        //至于为什么是LayoutParams.WRAP_CONTENT,那是因为我假设这个view的layout_width为wrap_content,因为如果是一个确切的值,那还有必要测量吗?
        Log.i("bqt", "手动measure后:" + view.getWidth() + "--" + view.getMeasuredWidth());//0--144。可以发现和我们设置的大小(50dp)是不符的
    }

}