監聽視圖樹 ViewTreeObserver 獲取View的寬高


前奏:在哪里可以獲取到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)是不符的
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM