tree view android,Android ViewTreeObserver使用总结

官方文档的描述ViewTreeObserver是用来监听一些全局变化的。

在 ViewTreeObserver 中,包含了以下几个接口:

interface ViewTreeObserver.OnGlobalFocusChangeListener

interface ViewTreeObserver.OnGlobalLayoutListener

interface ViewTreeObserver.OnPreDrawListener

interface ViewTreeObserver.OnScrollChangedListener

interface ViewTreeObserver.OnTouchModeChangeListener

ViewTreeObserver 注册一个观察者来监听视图树,当视图树的布局、视图树的焦点、视图树将要绘制、视图树滚动等发生改变时,ViewTreeObserver都会收到通知,ViewTreeObserver不能被实例化,可以调用View.getViewTreeObserver()来获得。

ViewTreeObserver继承关系:

public final class ViewTreeObserverextendsObject

java.lang.Object

android.view.ViewTreeObserver

ViewTreeObserver直接继承自Object.

ViewTreeObserver提供了View的多种监听,每一种监听都有一个内部类接口与之对应,内部类接口全部保存在CopyOnWriteArrayList中,通过ViewTreeObserver.addXXXListener()来添加这些监听,源码如下:

public final class ViewTreeObserver {

// Recursive listeners use CopyOnWriteArrayList

private CopyOnWriteArrayList mOnWindowFocusListeners;

private CopyOnWriteArrayList mOnWindowAttachListeners;

private CopyOnWriteArrayList mOnGlobalFocusListeners;

private CopyOnWriteArrayList mOnTouchModeChangeListeners;

private CopyOnWriteArrayList mOnEnterAnimationCompleteListeners;

// Non-recursive listeners use CopyOnWriteArray

// Any listener invoked from ViewRootImpl.performTraversals() should not be recursive

private CopyOnWriteArray mOnGlobalLayoutListeners;

private CopyOnWriteArray mOnComputeInternalInsetsListeners;

private CopyOnWriteArray mOnScrollChangedListeners;

private CopyOnWriteArray mOnPreDrawListeners;

private CopyOnWriteArray mOnWindowShownListeners;

// These listeners cannot be mutated during dispatch

private ArrayList mOnDrawListeners;

}

以OnGlobalLayoutListener为例,首先是定义接口:

public interface OnGlobalLayoutListener {

/**

* Callback method to be invoked when the global layout state or the visibility of views

* within the view tree changes

*/

public void onGlobalLayout();

}

将OnGlobalLayoutListener 添加到CopyOnWriteArray数组中:

/**

* Register a callback to be invoked when the global layout state or the visibility of views

* within the view tree changes

*

* @param listener The callback to add

*

* @throws IllegalStateException If {@link #isAlive()} returns false

*/

public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {

checkIsAlive();

if (mOnGlobalLayoutListeners == null) {

mOnGlobalLayoutListeners = new CopyOnWriteArray();

}

mOnGlobalLayoutListeners.add(listener);

}

移除OnGlobalLayoutListener,当视图树布局发生变化时不会再收到通知了:

/**

* Remove a previously installed global layout callback

*

* @param victim The callback to remove

*

* @throws IllegalStateException If {@link #isAlive()} returns false

*

* @deprecated Use #removeOnGlobalLayoutListener instead

*

* @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)

*/

@Deprecated

public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {

removeOnGlobalLayoutListener(victim);

}

/**

* Remove a previously installed global layout callback

*

* @param victim The callback to remove

*

* @throws IllegalStateException If {@link #isAlive()} returns false

*

* @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)

*/

public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {

checkIsAlive();

if (mOnGlobalLayoutListeners == null) {

return;

}

mOnGlobalLayoutListeners.remove(victim);

}

其他常用方法:

dispatchOnGlobalLayout():视图树发生改变时通知观察者,如果想在View Layout 或 View hierarchy 还未依附到Window时,或者在View处于GONE状态时强制布局,这个方法也可以手动调用。

/**

* Notifies registered listeners that a global layout happened. This can be called

* manually if you are forcing a layout on a View or a hierarchy of Views that are

* not attached to a Window or in the GONE state.

*/

public final void dispatchOnGlobalLayout() {

// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to

// perform the dispatching. The iterator is a safe guard against listeners that

// could mutate the list by calling the various add/remove methods. This prevents

// the array from being modified while we iterate it.

final CopyOnWriteArray listeners = mOnGlobalLayoutListeners;

if (listeners != null && listeners.size() > 0) {

CopyOnWriteArray.Access access = listeners.start();

try {

int count = access.size();

for (int i = 0; i < count; i++) {

access.get(i).onGlobalLayout();

}

} finally {

listeners.end();

}

}

}

dispatchOnPreDraw():通知观察者绘制即将开始,如果其中的某个观察者返回 true,那么绘制将会取消,并且重新安排绘制,如果想在View Layout 或 View hierarchy 还未依附到Window时,或者在View处于GONE状态时强制绘制,可以手动调用这个方法。

/**

* Notifies registered listeners that the drawing pass is about to start. If a

* listener returns true, then the drawing pass is canceled and rescheduled. This can

* be called manually if you are forcing the drawing on a View or a hierarchy of Views

* that are not attached to a Window or in the GONE state.

*

* @return True if the current draw should be canceled and resceduled, false otherwise.

*/

@SuppressWarnings("unchecked")

public final boolean dispatchOnPreDraw() {

boolean cancelDraw = false;

final CopyOnWriteArray listeners = mOnPreDrawListeners;

if (listeners != null && listeners.size() > 0) {

CopyOnWriteArray.Access 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;

}

ViewTreeObserver常用内部类:

内部类接口

备注

ViewTreeObserver.OnPreDrawListener

当视图树将要被绘制时,会调用的接口

ViewTreeObserver.OnGlobalLayoutListener

当视图树的布局发生改变或者View在视图树的可见状态发生改变时会调用的接口

ViewTreeObserver.OnGlobalFocusChangeListener

当一个视图树的焦点状态改变时,会调用的接口

ViewTreeObserver.OnScrollChangedListener

当视图树的一些组件发生滚动时会调用的接口

ViewTreeObserver.OnTouchModeChangeListener

当视图树的触摸模式发生改变时,会调用的接口

获得View高度的几种方式:

我们应该都遇到过在onCreate()方法里面调用view.getWidth()和view.getHeight()获取到的view的宽高都是0的情况,这是因为在onCreate()里还没有执行测量,需要在onResume()之后才能得到正确的高度,那么可不可以在onCreate()里就得到宽高呢?答:可以!常用的有下面几种方式:

1、通过设置View的MeasureSpec.UNSPECIFIED来测量:

int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);

view.measure(w, h);

//获得宽高

int viewWidth=view.getMeasuredWidth();

int viewHeight=view.getMeasuredHeight();

设置我们的SpecMode为UNSPECIFIED,然后去调用onMeasure测量宽高,就可以得到宽高。

2、通过ViewTreeObserver .addOnGlobalLayoutListener来获得宽高,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行:

//获得ViewTreeObserver

ViewTreeObserver observer=view.getViewTreeObserver();

//注册观察者,监听变化

observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

//判断ViewTreeObserver 是否alive,如果存活的话移除这个观察者

if(observer.isAlive()){

observer.removeGlobalOnLayoutListener(this);

//获得宽高

int viewWidth=view.getMeasuredWidth();

int viewHeight=view.getMeasuredHeight();

}

}

});

3、通过ViewTreeObserver .addOnPreDrawListener来获得宽高,在执行onDraw之前已经执行了onLayout()和onMeasure(),可以得到宽高了,当获得正确的宽高后,请移除这个观察者,否则回调会多次执行

//获得ViewTreeObserver

ViewTreeObserver observer=view.getViewTreeObserver();

//注册观察者,监听变化

observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

@Override

public boolean onPreDraw() {

if(observer.isAlive()){

observer.removeOnDrawListener(this);

}

//获得宽高

int viewWidth=view.getMeasuredWidth();

int viewHeight=view.getMeasuredHeight();

return true;

}

});

案例分析

xml布局如下

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:gravity="center"

android:layout_height="match_parent"

android:orientation="vertical"

android:id="@+id/layout"

tools:context="trs.com.viewtreeobserverdemo.MainActivity">

android:id="@+id/tv_show"

android:layout_width="match_parent"

android:layout_height="wrap_content" />

android:hint="et1"

android:tag="et1"

android:id="@+id/et_1"

android:layout_width="match_parent"

android:layout_height="wrap_content" />

android:tag="et2"

android:hint="et2"

android:layout_marginTop="10dp"

android:id="@+id/et_2"

android:layout_width="match_parent"

android:layout_height="wrap_content" />

android:text="test"

android:id="@+id/btn"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

让MainActivity实现相应接口

public class MainActivity extends AppCompatActivity implements

ViewTreeObserver.OnGlobalLayoutListener,

ViewTreeObserver.OnPreDrawListener, ViewTreeObserver.OnGlobalFocusChangeListener,

ViewTreeObserver.OnTouchModeChangeListener, View.OnClickListener

在onCreat中添加监听

EditText et_1,et_2;

TextView tv_show;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ViewTreeObserver vto = findViewById(R.id.layout).getViewTreeObserver();

et_1= (EditText) findViewById(R.id.et_1);

et_2= (EditText) findViewById(R.id.et_2);

vto.addOnGlobalLayoutListener(this);

vto.addOnPreDrawListener(this);

vto.addOnGlobalFocusChangeListener(this);

vto.addOnTouchModeChangeListener(this);

findViewById(R.id.btn).setOnClickListener(this);

tv_show= (TextView) findViewById(R.id.tv_show);

}

一.OnGlobalFocusChangeListener

首先测试ViewTreeObserver.OnGlobalFocusChangeListener,实现接口方法

代码

@Override

public void onGlobalFocusChanged(View oldFocus, View newFocus) {

if(oldFocus!=null) {

tv_show.setText("Focus change from " + oldFocus.getTag() + " to " + newFocus.getTag());

}else{

tv_show.setText( newFocus.getTag()+"get focus");

}

}

注意:在第一次进入页面的时候没有oldFoucs

效果

这个接口很简单就是监听focus的变化:

4f68d62c809b

image.png

二.OnPreDrawListener

OnPreDrawListener接口是在绘制界面前调用

代码

@Override

public boolean onPreDraw() {

et_1.setHint("set hint on onPreDraw ");

//Return true to proceed with the current drawing pass, or false to cancel.

//返回 true 继续绘制,返回false取消。

return true;

}

效果

4f68d62c809b

image.png

如果返回false的话,效果是这样的,界面没有绘制。

4f68d62c809b

image.png

关于OnPreDrawListener的使用,有一个例子就是CoordinatorLayout调用Behavior的onDependentViewChanged就是通过注册OnPreDrawListener接口,在绘制的时候检查界面是否发生变化,如果变化就调用Behavior的onDependentViewChanged。

源码

@Override

public void onAttachedToWindow() {

super.onAttachedToWindow();

resetTouchBehaviors();

if (mNeedsPreDrawListener) {

if (mOnPreDrawListener == null) {

mOnPreDrawListener = new OnPreDrawListener();

}

//注册OnPreDrawListener

final ViewTreeObserver vto = getViewTreeObserver();

vto.addOnPreDrawListener(mOnPreDrawListener);

}

if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {

// We're set to fitSystemWindows but we haven't had any insets yet...

// We should request a new dispatch of window insets

ViewCompat.requestApplyInsets(this);

}

mIsAttachedToWindow = true;

}

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {

@Override

//分发OnDependentViewChanged

dispatchOnDependentViewChanged(false);

return true;

}

}

void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {

final int layoutDirection = ViewCompat.getLayoutDirection(this);

final int childCount = mDependencySortedChildren.size();

for (int i = 0; i < childCount; i++) {

final View child = mDependencySortedChildren.get(i);

final LayoutParams lp = (LayoutParams) child.getLayoutParams();

// Check child views before for anchor

for (int j = 0; j < i; j++) {

final View checkChild = mDependencySortedChildren.get(j);

if (lp.mAnchorDirectChild == checkChild) {

offsetChildToAnchor(child, layoutDirection);

}

}

//判断是否发生变化

// Did it change? if not continue

final Rect oldRect = mTempRect1;

final Rect newRect = mTempRect2;

getLastChildRect(child, oldRect);

getChildRect(child, true, newRect);

if (oldRect.equals(newRect)) {

continue;

}

recordLastChildRect(child, newRect);

// Update any behavior-dependent views for the change

for (int j = i + 1; j < childCount; j++) {

final View checkChild = mDependencySortedChildren.get(j);

final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();

final Behavior b = checkLp.getBehavior();

if (b != null && b.layoutDependsOn(this, checkChild, child)) {

if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {

// If this is not from a nested scroll and we have already been changed

// from a nested scroll, skip the dispatch and reset the flag

checkLp.resetChangedAfterNestedScroll();

continue;

}

//调用onDependentViewChanged

final boolean handled = b.onDependentViewChanged(this, checkChild, child);

...

}

}

}

}

三.OnGlobalLayoutListener

Interface definition for a callback to be invoked when the global layout state

or the visibility of views within the view tree changes.

当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类

代码

1.在点击时改变EditText的可视性。

@Override

public void onClick(View v) {

if(et_1.isShown()){

et_1.setVisibility(View.GONE);

}else{

et_1.setVisibility(View.VISIBLE);

}

}

2.在onGlobalLayout显示EditText的可见性

@Override

public void onGlobalLayout() {

if(et_1.isShown()){

tv_show.setText("EditText1 显示");

}else{

tv_show.setText("EditText1 隐藏");

}

}

效果

4f68d62c809b

这里写图片描述

注意:在测试的时候发现使用

et_1.setVisibility(View.INVISIBLE);

时并不会触发OnGlobalLayoutListener而只能使用

et_1.setVisibility(View.GONE);

补充

可以使用OnGlobalLayoutListener获取控件宽高。

private int mHeaderViewHeight;

private View mHeaderView;

.....

mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(

new OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

mHeaderViewHeight = mHeaderView.getHeight();

mHeaderView.getViewTreeObserver()

.removeGlobalOnLayoutListener(this);

}

});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值