一、ViewTreeObserver简介
1.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 {@link android.view.View#getViewTreeObserver()}
* for more information.
*/
2.ViewTreeObserver是一个被final修饰的类,所以不能被继承。
3.ViewTreeObserver由于是view层的,所以不能在应用程序中直接被实例化,我们可以使用getViewTreeObserver()方法获取它的实例化对象。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 {@link ViewTreeObserver#isAlive()}.
*
* @return The ViewTreeObserver for this view's hierarchy.
*/
二、常用方法
//当在一个视图树中的焦点状态发生改变时的回调函数
ViewTreeObserver.OnGlobalFocusChangeListener
//当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时调用的回调函数
ViewTreeObserver.OnGlobalLayoutListener
//当一个视图树将要绘制时调用的回调函数
ViewTreeObserver.OnPreDrawListener
//当一些组件发生滚动时调用的回调函数
ViewTreeObserver.OnScrollChangedListener
//当一个视图树的触摸模式发生改变时调用的回调函数
ViewTreeObserver.OnTouchModeChangeListener
// 当前ViewTreeObserver是否可用,不可用时,任何方法调用都会报错。
getViewTreeObserver().isAlive()
// 当一个view贴到window上时的回调
addOnWindowAttachListener()
//相应的注销监听的方法
removeOnGlobalFocusChangeListener()
removeOnScrollChangedListener()
removeOnTouchModeChangeListener()
//当整个布局发生改变时通知相应的注册监听器。
//如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在GONE状态下,它可以被手动的调用
dispatchOnGlobalLayout ()
//当一个视图树将要绘制时通知相应的注册监听器。如果这个监听器返回true,则这个绘制将被取消并重新计划。
// 如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在一个GONE状态下,它可以被手动的调用
dispatchOnPreDraw()
三、使用场合
一般用在我们要实时获取一个view的高度和宽度后做一些动效和优化的场合。因为我们在oncreate()中无法获得一个view的高度和宽度,View组件布局要在onResume()回调后完成。所以我们可以使用getViewTreeObserver()获取观察者实例后调用相应方法来获得view的真实宽度或者高度。
下面我举两个例子:
1.仿知乎ScrollView滚动改变标题栏文字(红框中的部分)和透明度
private void getTopTitleHeight() {
//获取顶部title的高度
tv_title.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
height = tv_title.getMeasuredHeight();
BLog.i("----------height=" + height);
return true;
}
});
}
上面的代码是为了获取标题的高度,获取后,等到scrollview向下滑动到看不到标题内容时,做一些效果显示。
2.使用FlowLayout显示热门标签,超过3行可以折叠展开显示
/**
* 初始化热门话题标签
*
* @param data
*/
private FlowLayout mFlTagSubject;
private void initFlowLayoutData(final List<SubjectBean> data) {
for (int i = 0; i < data.size(); i++) {
TextView mTvTag = (TextView) inflater.inflate(R.layout.item_textview_topic_tag, null);
mTvTag.setText(data.get(i).title);
//没有父布局,所以先要给子view设置宽高
mTvTag.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, DensityUtils.dp2px(getActivity(), 30)));
mFlTagSubject.addView(mTvTag, params);
}
mFlTagSubject.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mFlTagSubject.getViewTreeObserver().removeOnPreDrawListener(this);
setFlowLayoutHeight();
return true;
}
});
mTopicData.addAll(data);
}
private void setFlowLayoutHeight() {
mMaxLine = mFlTagSubject.getLineNumber();
mLongHeight = mFlTagSubject.getViewHeight();
mShortHeight = mFlTagSubject.getOneLineHeight() * mShowLine;
BLog.e("tag", "initFlowLayoutData maxLine = " + mMaxLine);
BLog.e("tag", "initFlowLayoutData mLongHeight = " + mLongHeight);
BLog.e("tag", "initFlowLayoutData mShortHeight = " + mShortHeight);
if (mLongHeight <= mShortHeight) {
//热门标签实际高度小于3行时不显示折叠开关
mImgToogle.setVisibility(View.GONE);
mLayoutParams.height = mLongHeight;
} else {
//热门标签实际高度超过3行时显示折叠开关,打开开关可以展开显示全部
mImgToogle.setVisibility(View.VISIBLE);
mLayoutParams.height = mShortHeight;
}
mFlTagSubject.setLayoutParams(mLayoutParams);
}
特别注意:addOnPreDrawListener()这个方法会被多次调用(其他几个方法也类似),所以要根据实际情况在获得真实高度之后及时移除监听。mFlTagSubject.getViewTreeObserver().removeOnPreDrawListener(this),使用它就可以移除监听。