btonf - Love technology

技术小站
知识干货分享站点

View宽高获取时机

View的绘制过程

View绘制过程为measure(测量),layout(决定位置),draw(绘制)
由于measure方法为final类型,所以我们无法去重写该方法,但是在测量结束后会回调onMeasure方法,在该方法中可以获取到测量宽/高,之所以说是测量,是因为可能由于某种原因导致最后显示出的宽高并不和测量的一致,但是大部分情况下实际高度等于测量宽/高.
不推荐在onMeasure中去获取测量宽高,某些情况下,view可能会多次测量才会获取到测量宽/高,在这种情况下获取到的很大概率是不准确的.
推荐在onLayout中获取测量宽/高或者最终宽/高


测量宽高的时机

前言
例如在Activity中去获取view的宽高,很多人会选择在onCreateonPause中去获取,但是这样很有可能测量获得的宽高为0
TIPS:View的绘制过程并不是和Activity的生命周期同步的,在Activity回调onCreateonPause方法时并不能保证View已经绘制完毕

Activity/View#onWindowFocusChanged

此方法的含义为:View已经绘制完毕,已经获取到焦点.当然,此时的宽高也已经确定了,但是需要注意,在视图获取焦点或者失去焦点都会被调用一次,很容易造成频繁的调用

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus){
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();
    }
}

view.post(runnable)

通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了.

view.post(new Runnable() {
   @Override
    public void run() {
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();     
    }
});

ViewTreeObserver

使用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.measure

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
    此时的SpecSizeparentSize,然而此时无法知道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
    表格中的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);

此处实际实验得知....设置为-1根本不能编译通过.....明确给出了错误提示Value must be >= 0

view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
本原创文章未经允许不得转载 | 当前页面:btonf - Love technology » View宽高获取时机

评论