您的位置:首页 > 其它

ViewTreeObserver的使用

2016-04-13 11:02 429 查看

序言

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 View.getViewTreeObserver() for more information.

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

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

interface ViewTreeObserver.OnGlobalFocusChangeListener

interface ViewTreeObserver.OnGlobalLayoutListener

interface ViewTreeObserver.OnPreDrawListener

interface ViewTreeObserver.OnScrollChangedListener

interface ViewTreeObserver.OnTouchModeChangeListener

本文将测试除 ViewTreeObserver.OnScrollChangedListener 外的四个接口

xml布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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">
<TextView
android:id="@+id/tv_show"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:hint="et1"
android:tag="et1"
android:id="@+id/et_1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
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" />

<Button
android:text="test"
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>


让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的变化。



二.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;
}


效果



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



关于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 隐藏");
}
}


效果



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

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);
}
});


关于OnTouchModeChangeListener的使用目前还没有搞清楚,以后再回来补充吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: