需要提前知道的一些东西
- Android中获取View的宽度或者高度,可以通过View自带的方法
getWidth()
、getHeight()
,但这仅限于layout_width
和layout_height
的值是具体的dp
或者match_parent
,如果值是wrap_content
,那么直接调用getWidth()
、getHeight()
方法,可能返回的会是0。
- 直接调用
getWidth()
、getHeight()
可能返回0的原因是,View可能还没有被添加到界面上(这里添加到界面上是指View执行了onMeasure方法),View添加到界面上之后,才计算完宽度和高度,所以如果宽度或高度如果设置wrap_content
,那么需要在View添加到界面上之后调用,才会返回具体的值。
计算View的宽度/高度
通过getMeasureWidth()/getMeasureHeight()计算
- 这里以计算高度为例
- 如果高度设置为了wrap_content,可以通过getMeasureHeight()来计算高度
textView.post(new Runnable(){
@Override
public void run(){
int widthMeasureMode = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
int heightMeasureMode = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
textView.measure(widthMeasureMode, heightMeasureMode );
int measureHeight = textView.getMeasureHeight();
}
})
- 测量模式
- Android的测量模式有三种,根据不同的测量模式,会按照不同的方式测量宽高
EXACTLY
:表示有具体数值,如果宽高设置为具体的数值或者match_parent,我们可以使用EXACTLY的测量模式
AT_MOST
:表示最大程度的增长,此时父布局将自身可用的最大宽度或者高度传给子View,用来代表子View的最大宽度或高度。如果宽高设置为wrap_content,那么此时表示宽度或者高度不确定,因此可以用AT_MOST模式,让Android去测量它的宽度或高度,但是它不会超过父布局的最大宽度或高度。
UNSPECIFIED
:表示可以无限增长。例如TextView如果按照这种方式去测量宽度,那么不管文本有多长,最终都会被当作单行去测量宽度。所以如果你想去测量TextView的高度,而你的宽度测量模式用了UNSPECIFIED,那么不管你文本有多长,它永远只返回单行的高度,因为Android认为它只有一行。
- 注意点
- 上面用post表示将Runnable放到TextView的消息队列尾部,在TextView添加到界面上之后,会执行这个Runnable
- 上面的方式只适用于TextView高度设置成了wrap_content,而且文本内容不会发生变化或者只在初始化的时候变化一次的情况。如果TextView的文本在使用过程中会发生变化,那么getMeasureHeight()返回的值,只在第一次是正确的,后续文本内容发生变化,它永远返回上一次的高度。这个原因出于我自己的理解,可能是因为在第二次文本变化的时候,post的任务的执行,在TextView布局发生变化之前,所以导致获取的高度值是旧值。
- 上面的方式需要在TextView在初始化的时候,就是visible的情况,如果初始化的时候是Gone,那么可能在第一次getMeasureHeight的时候,返回的值是0,后续变化返回正确的高度,原因同上。
通过ViewTreeObserver中计算高度
ViewTreeObserver
可以addOnGlobalLayoutListener()
,该方法有个onGlobalLayout
回调,这个回调方法会在View添加到界面上之后调用
- 也就是说,我们可以在
onGlobalLayout
中直接通过getHeight()
、getWidth()
来获取高度或者宽度。
ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
viewTreeObserver .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
textView.requestLayout();
float height = (float) textView.getHeight();
topViewTreeObserver.removeOnGlobalLayoutListener(this);
}
});
requestLayout()
- 一个View调用了requestLayout之后,会给当前View设置一个
FORCE_LAYOUT
标记,由此向ViewParent请求布局,这样从这个View开始向上一直requestLayout,最终到达ViewRootImpl
。
- ViewRootImpl发现请求了布局,那么就会调用measure方法,确认当前View是否有FORCE_LAYOUT标记,如果有,则会直接进行重新布局。并且设置标记
LAYOUT_REQUIRED
。
- 所以只要View调用了requestLayout方法,那么会同时去调用measure、onMeasure、layout、onLayout、draw、onDrawa方法(不会马上调用,请求到了ViewRootImpl之后,ViewRootImpl调用measure方法才会一一调用),重新去计算布局。
- 注意点
- 上面的方式,在TextView的文本内容发生变化的时候,能够实时获取到TextView的高度,就是因为调用了requestLayout方法,重新去布局、计算宽高,所以使得TextView在getHeight()的时候,获取到的就是最新的,而不是上一次的。
- 如果去掉requestLayout方法,那么他会有和第一种方法一样的问题。