View绘制过程为measure
(测量),layout
(决定位置),draw
(绘制)
由于measure
方法为final
类型,所以我们无法去重写该方法,但是在测量结束后会回调onMeasure
方法,在该方法中可以获取到测量宽/高,之所以说是测量,是因为可能由于某种原因导致最后显示出的宽高并不和测量的一致,但是大部分情况下实际高度等于测量宽/高.
不推荐在onMeasure
中去获取测量宽高,某些情况下,view可能会多次测量才会获取到测量宽/高,在这种情况下获取到的很大概率是不准确的.
推荐在onLayout
中获取测量宽/高或者最终宽/高
前言
例如在Activity中去获取view的宽高,很多人会选择在onCreate
或onPause
中去获取,但是这样很有可能测量获得的宽高为0
TIPS:View的绘制过程并不是和Activity的生命周期同步的,在Activity回调onCreate
和onPause
方法时并不能保证View已经绘制完毕
此方法的含义为:View已经绘制完毕,已经获取到焦点.当然,此时的宽高也已经确定了,但是需要注意,在视图获取焦点或者失去焦点都会被调用一次,很容易造成频繁的调用
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
int width = view.getMeasuredWidth();
int width = view.getMeasuredHeight();
}
}
通过post可以将一个runnable
投递到消息队列的尾部,然后等待Looper
调用此runnable
的时候,View也已经初始化好了.
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int width = view.getMeasuredHeight();
}
});
使用ViewTreeObserver
的回调方法,比如OnGlobalLayoutListener
接口,当View树的状态发生改变,或者可见性发生改变时,都会回调onGlobalLayout
方法,但是需要注意,随着状态发生改变,onGlobalLayout
会被调用多次
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int width = view.getMeasuredHeight();
}
});
view的测量需要根据当前view的LayoutParams
和父容器的MeasureSpec
手动进行测量
MeasureSpec
代表一个32位的int值,高2位代表SpecMode
,低30位代表SpecSize
SpecMode
:EXACTLY
, AT_MOST
,UNSPECIFIED
SpecSize
:dp/px
,match_parent
,wrap_content
parentSpecMode EXACTLY |
parentSpecMode AT_MOST |
parentSpecMode UNSPECIFIED |
|
---|---|---|---|
childLayoutParams dp/px |
childSize EXACTLY |
childSize EXACTLY |
childSize EXACTLY |
childLayoutParams match_parent |
parentSize EXACTLY |
parentSize AT_MOST |
0 UNSPECIFIED |
childLayoutParams wrap_content |
parentSize AT_MOST |
parentSize AT_MOST |
0 UNSPECIFIED |
根据view的LayoutParams
分为如下几种情况
match_parent
此时的SpecSize
为parentSize
,然而此时无法知道parentSize
的大小,无法构造出MeasureSpec
具体的数值dp/px
假设宽高都是100px,可以构造以下MeasureSpec
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
wrap_content
行中的parentSize
表示view的最大宽/高为parentSize
,我们可以使用View理论上支持的最大值(1<<30)-1
(30位二进制)去构造是合理的int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
错误的用法
违背了系统的内部实现规范(无法通过错误的MeasureSpec
去得出合法的SpecMode
,从而导致measure
过程出错),并且不能保证一定能measure
出正确的结果
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec,heightMeasureSpec);
PS:此处实际实验得知….设置为-1根本不能编译通过…..明确给出了错误提示Value must be >= 0
,不知道书中为什么还要列举这个…..
view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
本文参考Android开发艺术探索
进行整理