android解析布局树,Android Viewtreeobserver解析
ViewTreeObserver是view事件的观察者。这个观察者用来监听视图树,会监听视图树发生全局变化时发出的通知。这里指的全局 事件包括而且不局限在以下几个:整个视图树的布局变化,开始绘制视图,触摸模式改变等等。
ViewTreeObserver是不能被应用程序实例化的,因为它是由视图提供的,通过view.getViewTreeObserver()获取。
ViewTreeObserver提供了view的很多种监听,每一种监听在ViewTreeObserver中都有一个内部类接口来定义。查看源码可以看到 一共有11个内部类,全部保存在CopyOnWriteArrayList数组中,通过ViewTreeObserver.addXXXListener()来添加这些监听, 所以这11个接口对应了11个add方法,但是有一些方法是还没有开放的,用@hide隐藏了。我目前测试的源码在compileSdkVersion 22的基础上。api的概述
下面介绍其中常用的几个内部类:
[代码]java代码:01
02
03
04
05
06
07
08
09
10interface ViewTreeObserver.OnGlobalFocusChangeListener
//当在一个视图树中的焦点状态发生改变时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnGlobalLayoutListener
//当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnPreDrawListener
//当一个视图树将要绘制时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnScrollChangedListener
//当一个视图树中的一些组件发生滚动时,所要调用的回调函数的接口类
interface ViewTreeObserver.OnTouchModeChangeListener
//当一个视图树的触摸模式发生改变时,所要调用的回调函数的接口类
他们对应的add方法如下:
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**注册一个回调函数,当在一个视图树中的焦点状态发生改变时调用这个回调函数。
* 参数 listener 将要被添加的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void addOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener listener)
/**注册一个回调函数,当在一个视图树中全局布局发生改变或者视图树中的某个视图的可视状态发生改变时调用这个回调函数。
*参数 listener 将要被添加的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
/**注册一个回调函数,当一个视图树将要绘制时调用这个回调函数。
*参数 listener 将要被添加的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
/**注册一个回调函数,当一个视图发生滚动时调用这个回调函数。
*参数 listener 将要被添加的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)
/**注册一个回调函数,当一个触摸模式发生改变时调用这个回调函数。
*参数 listener 将要被添加的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
还可以调用remove方法删除监听
他们对应的add方法如下
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**移除之前已经注册的全局布局回调函数。
*参数 victim 将要被移除的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)
/**移除之前已经注册的焦点改变回调函数。
*参数 victim 将要被移除的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void removeOnGlobalFocusChangeListener (ViewTreeObserver.OnGlobalFocusChangeListener victim)
/**移除之前已经注册的预绘制回调函数。
*参数 victim 将要被移除的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void removeOnPreDrawListener (ViewTreeObserver.OnPreDrawListener victim)
/**移除之前已经注册的滚动改变回调函数。
*参数 victim 将要被移除的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void removeOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener victim)
/**移除之前已经注册的触摸模式改变回调函数
*参数 victim 将要被移除的回调函数
*异常 IllegalStateException 如果isAlive() 返回false
*/
public void removeOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener victim)
其他的常用方法:
[代码]java代码:01
02
03
04
05
06
07
08
09
10//当整个布局发生改变时通知相应的注册监听器。如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在GONE状态下,它可以被手动的调用
public final void dispatchOnGlobalLayout ()
/**当一个视图树将要绘制时通知相应的注册监听器。如果这个监听器返回true,则这个绘制将被取消并重新计划。如果你强制对视图布局或者在一个没有附加到一个窗口的视图的层次结构或者在一个GONE状态下,它可以被手动的调用
*返回值 当前绘制能够取消并重新计划则返回true,否则返回false。
*/
public final boolean dispatchOnPreDraw ()
/**指示当前的ViewTreeObserver是否可用(alive)。当observer不可用时,任何方法的调用(除了这个方法)都将抛出一个异常。如果一个应用程序保持和ViewTreeObserver一个历时较长的引用,它应该总是需要在调用别的方法之前去检测这个方法的返回值。
*返回值 但这个对象可用则返回true,否则返回false
*/
public boolean isAlive ()
用法举例
用于测量view的宽高
先写一个自定义view如下:
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18public class MyImageView extends ImageView {
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyImageView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d("===MyImageView","onMeasure我被调用了"+System.currentTimeMillis());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("===MyImageView", "onDraw我被调用了"+System.currentTimeMillis());
}
}
MainActivity布局文件如下:
[代码]xml代码:1
2
MainActivity中的代码:
[代码]java代码:1
2
3
4
5
6
7
8@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyImageView myImageView = (MyImageView) findViewById(R.id.imageview);
Log.d("===MainActivity", "onCreate执行完毕..myImageView " +
"height:" + myImageView.getMeasuredHeight() + " ,width:" + myImageView.getMeasuredWidth());
}
首先应该知道的是在MainActivity的onCreate中调用myImageView.getHeight()的值是为0的,因为在onCreate中还没有调用onLayout(), 具体原因参考,我在之前的日志:深入了解android view绘制机制 .
简单的说就是,在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了,getWidth()方法和getMeasureWidth() 方法到底有什么区别呢?它们的值好像永远都是相同的。其实它们的值之所以会相同基本都是因为布局设计者的编码习惯非常好, 实际上它们之间的差别还是挺大的。首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout() 过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的, 而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
一般有三种方法可以在onCreate中计算控件宽高:
1.第一种方法:
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyImageView myImageView = (MyImageView) findViewById(R.id.imageview);
int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED);
myImageView.measure(w, h);
Log.d("===MainActivity", "onCreate执行完毕..myImageView " +
"height:" + myImageView.getMeasuredHeight() + " ,width:" + myImageView.getMeasuredWidth());
}
可以看到在onCreate手动调用了onMeasure,这样就可以通过getMeasuredHeight()取得宽度和高度了。
2.第二种方法:
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyImageView myImageView = (MyImageView) findViewById(R.id.imageview);
int height = 0;
int width = 0 ;
ViewTreeObserver vto = myImageView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
int height = myImageView.getMeasuredHeight();
int width = myImageView.getMeasuredWidth();
Log.d("===PreDrawListener", "PreDrawListener..myImageView " +
"height:" + height + " ,width:" + width);
return true;
}
});
Log.d("===MainActivity", "onCreate执行完毕..myImageView " +
"height:" + height + " ,width:" + width);
}
通过设置OnPreDrawListener监听,在view绘制之前调用在onPreDraw方法里,在onPreDraw方法里获得view的宽高。 注意这里 onPreDraw调用了多次,是因为没有调用myImageView.getViewTreeObserver().removeOnPreDrawListener(this); 加上remove方法就可以让onPreDraw只调用一次。
3.第三种方法:
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyImageView myImageView = (MyImageView) findViewById(R.id.imageview);
ViewTreeObserver vto = myImageView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
myImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.d("===OnGlobalLayout", "OnGlobalLayoutListener..myImageView " +
"height:" + myImageView.getHeight() + " ,width:" + myImageView.getWidth());
}
});
Log.d("===MainActivity", "onCreate执行完毕..myImageView " +
"height:" + myImageView.getHeight() + " ,width:" + myImageView.getWidth());
}
和方法二类似,只是换成了OnGlobalLayoutListener。这个OnGlobalLayoutListener在使用前调用了removeGlobalOnLayoutListener, 所以只触发了一次。
Activity跳转动画
ViewTreeObserver还可以用来监听根布局,用来实现Activity跳转动画,核心代码如下:
[代码]java代码:01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
rootView = findViewById(R.id.root);
if (savedInstanceState == null) {
rootView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
rootView.getViewTreeObserver().removeOnPreDrawListener(this);
startRootAnimation();
return true;
}
});
}
}
可以加入自己喜欢的动画,实现不同的效果,
测量软键盘状态和高度
[代码]java代码:1
2
3
4
5
6
7
8
9ViewTreeObserver.OnGlobalLayoutListener mListener = new ViewTreeObserver
.OnGlobalLayoutListener() {
public void onGlobalLayout() {
Rect r1 = new Rect();
root.getWindowVisibleDisplayFrame(r1);
Log.e("TAG",r1.bottom+"") ;
}
};
root.getViewTreeObserver().addOnGlobalLayoutListener(mListener);
在根布局加入GlobalLayoutListener监听,通过getWindowVisibleDisplayFrame方法可以观察可见区域的变化,键盘打开后 会影响可见区域的大小,导致Rect的底部r1.bottom变小.
android解析布局树,Android Viewtreeobserver解析相关推荐
- android 首页布局变换,Android XML布局与View之间的转换
Android的布局方式有两种,一种是通过xml布局,一种是通过java代码布局,两种布局方式各有各的好处,当然也可以相互混合使用.很多人都习惯用xml布局,那xml布局是如何转换成view的呢?本文 ...
- android 减少布局层级,Android 布局优化
布局优化主要从以下几点进行着手 减少布局层次 和 复杂度 优化绘制流程 按需加载布局 减少布局层次 和 复杂度 首先我们可以通过以下工具分析界面布局的结构 查看布局树工具:Hierarchy View ...
- android 线性布局位置,android – 如何在线性布局中更改视图的位置.
所以我有一个已经填充了子项的线性布局.有没有办法改变其中一个孩子的位置? 如果有任何帮助的话,我正试图交换他们之间的观点. final LinearLayout parrent = (LinearLa ...
- android 获取布局textview,android – 获取TextView中文本的位置
看看几个Paint方法: getTextBounds()和 measureText.我们可以使用它们来确定TextView中文本的偏移量.确定TextView中的偏移后,我们可以将其添加到TextVi ...
- android局部布局替换,Android 局部布局替换的实现方式
最近再搞远程视频的功能,其中要实现加载视频.加载视频失败.加载成功的局部布局替换,查阅相关资料,找到一种投机取巧的方式. 首先分别写这三种效果的子布局,分别为 top_remotetreate.xml ...
- android圆角布局阴影,Android 布局阴影实现
最近项目要求,ui有很多有关于阴影的设计要求,网上找了些实现方式,但都不是很理想.现在闲下来了,就寻思着自己写个阴影布局耍耍,以备后用.先说道说道我找到的几种阴影实现方式: 系统阴影 Andorid ...
- android ui布局适配,Android适配全面总结(一)----屏幕适配
前言 Android适配是一个老生常谈的问题,很多程序员觉得很恶心,不愿意做适配,但是又不得不做.然后老板说,这位兄弟,做好了,今天晚饭给你加个鸡腿,然后程序员开始找各种资料,忙活起来了,最终在苦逼的 ...
- android滚动条布局横向,Android自定义ViewGroup实现可滚动的横向布局(2)
这里直接代码: package com.example.libingyuan.horizontallistview.ScrollViewGroup; import android.content.Co ...
- android相对布局代码,Android基础_3 Activity相对布局(示例代码)
相对布局要比前面讲的线性布局和表格布局要灵活一些,所以平常用得也是比较多的.相对布局控件的位置是与其周围控件的位置相关的,从名字可以看出来,这些位置都是相对的,确定出了其中一个控件的位置就可以确定另一 ...
最新文章
- html点击保持,如何保持:点击元素后的活动CSS样式
- dom4j 使用总结
- 工作66:storage区别
- java implements interface_java接口(interface)与现实(implements)
- 神奇的“弓箭手悖论”,为什么说箭是游出去的?射箭不能瞄准目标
- 编译 framework.jar包
- shell学习总结(1-4)
- 【UML】UML基础教程之顺序图、协作图、状态图、活动图、构件图、部署图
- 网吧服务器常用设置维护工具,某某网吧专用维护工具
- m4125idn如何扫描_京瓷ECOSYS M4125idn驱动
- php怎么写炫彩字,Photoshop打造超酷的炫彩字
- 修改css样式后刷新网页无改变
- 我学习的三种三栏(左中右)布局方法
- java数组:排好序的数组。现输入一个数,要求按原来的规律将它插入数组中。`
- linuxoracle图形界面无法跳出_Linux 7图形化安装Oracle或者其他软件,打不开图形界面的问题 | 信春哥,系统稳,闭眼上线不回滚!...
- 新库上线 | CnOpenDataA股上市公司IPO申报发行文本数据
- 当前企业财务报表分析存在的问题
- 哈尔滨小学计算机上课时间,哈市中小学各校新学期作息时间调整汇总,看看有没有你的学校!...
- 孰能生巧啊!只有滚瓜烂熟才能活学活用啊!所以熟练有时不是体力劳动
- 操作系统第二章笔记---计算机系统结构
热门文章
- markdown、LaTeX输入自定义算符Res
- 跑代码时出错:tensorflow.python.framework.errors_impl.UnknownError: 2 root error(s) found. (0) Unknown……
- centos7.0查看IP
- 动态规划算法之:最长公共子序列 最长公共子串(LCS)
- ES6_类_note
- python编写交互界面查分app_Django项目中model的数据处理以及页面交互方法
- VS code 快捷键
- java解析csv文件写入mysql_java读取cvs文件并导入数据库
- python创建字典和包的区别_python之路—模块和包
- oracle 服务名丢失,win2003 oracle服务丢失后恢复的一个例子