1、ViewTree
首先我们来介绍一下 ViewTree 也就是视图树是什么。
View 和 ViewGroup 是 Android UI 的基本组件, 而 ViewGroup 作为容器,可以包含一组 View, 并且 ViewGroup 其本身就是 View 的扩展。
而各种不同的 Widgets 像 TextView,Button 等等也是View的扩展,只不过是放在各种 Layout 里,比如 LinearLayout,RelativeLayout。而 Layout 却是 ViewGroup 的子类。
而 ViewTree 就是各种 View 和 ViewGroup 放在一个 Layout 里组成的树形结构。我们用 XML 编写的布局就是依照 ViewTree 结构层层叠加的。
2、ViewTreeObserver
ViewTreeObserver 是一个注册监听视图树的观察者(observer),会监听视图树发生全局变化时发出的通知。这个全局事件包括整个树的布局,从绘画过程开始,触摸模式的改变等等。
ViewTreeObserver不能够被应用程序实例化,因为它是由视图提供,通过 view.getViewTreeObserver() 方法获取。
简单的说,这是个 view 事件的观察者。要注意的是它的初始化就是调用View.getViewTreeObserver(),所以要使用 ViewTreeObserver 的方法也是使用这样来获得 ViewTreeObserver 对象再调用方法。
3、内部接口
名字 | 说明 |
---|---|
OnDrawListener | 当在一个视图树绘制时,所要调用的回调函数的接口类,与OnPreDrawListener不同的是,不能在这个接口中取消绘制过程 |
OnGlobalFocusChangeListener | 当在一个视图树中的焦点状态发生改变时,所要调用的回调函数的接口类 |
OnGlobalLayoutListener | 当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类 |
OnPreDrawListener | 当一个视图树将要绘制时,所要调用的回调函数的接口类 |
OnScrollChangedListener | 当一个视图树中的一些组件发生滚动时,所要调用的回调函数的接口类 |
OnTouchModeChangeListener | 当一个视图树的触摸模式发生改变时,所要调用的回调函数的接口 |
4、公共方法
1.
/*
* 注册一个回调函数,当在一个视图树绘制时调用这个回调函数。
*/
public void addOnDrawListener (ViewTreeObserver.OnDrawListener listener)
2.
/*
* 注册一个回调函数,当在一个视图树中的焦点状态发生改变时调用这个回调函数。
*/
public void addOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener listener)
3.
/*
* 注册一个回调函数,当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时调用这个回调函数。
*/
public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
4.
/*
* 注册一个回调函数,当一个视图树将要绘制时调用这个回调函数。
*/
public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
5.
/*
* 注册一个回调函数,当一个视图发生滚动时调用这个回调函数。
*/
public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)
6.
/*
* 注册一个回调函数,当一个触摸模式发生改变时调用这个回调函数。
*/
public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
7.
//告知相应监听器,视图绘制开始了
public final void dispatchOnDraw()
8.
/** 当整个布局发生改变时通知相应的注册监听器。
如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在GONE状态下,它可以被手动的调用 **/
public final void dispatchOnGlobalLayout()
9.
/*
* 当一个视图树将要绘制时通知相应的注册监听器。
* 如果这个监听器返回true,则这个绘制将被取消并重新计划。
* 如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在一个GONE状态下,它可以被手动的调用。
* 返回值 当前绘制能够取消并重新计划则返回true,否则返回false。
*/
public final boolean dispatchOnPreDraw ()
10.
/*
* 指示当前的ViewTreeObserver是否可用(alive)。
* 当observer不可用时,任何方法的调用(除了这个方法)都将抛出一个异常。
* (异常 IllegalStateException 如果isAlive() 返回false)
* 如果一个应用程序保持和ViewTreeObserver一个历时较长的引用,它应该总是需要在调用别的方法之前去检测这个方法的返回值。
* 返回值 但这个对象可用则返回true,否则返回false
*/
public boolean isAlive ()
11.
/*
* 移除之前已经注册的全局布局回调函数。
* 参数 victim 将要被移除的回调函数
*/
public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
12.
/*
* 移除之前已经注册的视图绘制回调函数。
*/
public void removeOnDrawListener (ViewTreeObserver.OnDrawListener victim)
13.
/*
* 移除之前已经注册的焦点改变回调函数。
*/
public void removeOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener victim)
14.
/*
* 移除之前已经注册的预绘制回调函数。
*/
public void removeOnPreDrawListener (ViewTreeObserver.OnPreDrawListener victim)
15.
/*
* 移除之前已经注册的滚动改变回调函数。
*/
public void removeOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener victim)
16.
/*
* 移除之前已经注册的触摸模式改变回调函数
*/
public void removeOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener victim)
5、实例
1、获取控件宽高
public class MainActivity extends AppCompatActivity {
private MyImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (MyImageView) findViewById(R.id.id_iv);
mImageView.setImageResource(R.drawable.t1);
//使用完必须撤销监听(只测量一次),否则,会一直不停的不定时的测量
ViewTreeObserver observer = mImageView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
final int w = mImageView.getMeasuredWidth();
final int h = mImageView.getMeasuredHeight();
Log.i("TAG", "width=" + w + " height=" + h);
}
});
}
}
TAG: width=768 height=1110
当前的 ImageView 占据了整个屏幕,而我的虚拟机设置的分辨率是 768 * 1366 去掉标题栏正好是1100。
这种方法无法像第一种方法那样通过一个函数返回值,因为它是基于 listener 的,OnGlobalLayoutListener 的 onGlobalLayout() 被回调之前是没有值的。由于布局状态可能会发生多次改变,因此onGlobalLayout() 可能被回调多次,所以我们在第一次获得值之后就将 listener 注销掉。
2、启动帧动画
mImageView = (MyImageView) findViewById(R.id.id_iv);
final AnimationDrawable anim = (AnimationDrawable) mImageView.getBackground();
OnPreDrawListener opd=new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
anim.start();
return true; //注意此行返回的值
}
};
mImageView.getViewTreeObserver().addOnPreDrawListener(opd);
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/t1"
android:duration="1000" />
<item
android:drawable="@drawable/t2"
android:duration="1000" />
<item
android:drawable="@drawable/t3"
android:duration="1000" />
</animation-list>
<com.ht.motiontest.MyImageView
android:id="@+id/id_iv"
android:background="@drawable/anim"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="200dp" />
关于帧动画的使用,不了解的朋友可以看我的博客Android–各种Drawable介绍。
3、监听焦点的变化
public class MainActivity extends AppCompatActivity {
private TextView mText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = (TextView) findViewById(R.id.id_tv);
OnGlobalFocusChangeListener ogl = new OnGlobalFocusChangeListener() {
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
if (oldFocus != null && newFocus != null) {
mText.setText("Focus \nFROM:\t" + oldFocus.toString() + "\n TO:\t" + newFocus.toString());
}
}
};
mText.getViewTreeObserver().addOnGlobalFocusChangeListener(ogl);
}
}
<?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:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.ht.motiontest.MainActivity">
<EditText
android:id="@+id/id_et"
android:text="hello world"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
android:text="hi"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:text=""
android:id="@+id/id_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
onGlobalFocusChanged() 是接口 OnGlobalFocusChangeListener 中定义的方法。当焦点发生变化时,会触发这个方法的执行。需要注意的是,布局中的任何一个view添加了这个监听,该布局的任意控件的焦点变化都能够监听的到。
所以可以看到当我们为 TextView 添加 listener 后,无论是第一个还是第二个 EditView,焦点一变化 TextView 的文字就会改变,说明被监听器监听到了。
结束语:本文仅用来学习记录,参考查阅。