【转】Android 获取 View 宽高的常用正确方式,避免为零

原文地址:http://blog.csdn.net/growing_tree/article/details/69220191

相信有很多朋友都有过在 Activity 中通过 getWidth() 之类的方法获取 View 的宽高值,可能在 onCreate() 生命周期方法中,也可能在 onResume() 生命周期方法中。然而,不幸的是,并不能获取所要的结果,宽高值均为 0。

如果对 View 的绘制显示流程熟悉的话,就会知道问题所在。我们知道,在自定义 View 时,通常都要重写 onMeasure、onLayout、onDraw 这几个方法。同理,Activity 的内容显示到设备上时,这些 View 也要经历这些阶段。

所以,当我们在 Activity 的生命周期方法中直接获取 View 的宽高时,View 也许还没执行完 measure 阶段,那么自然获取到的宽高结果为 0。这也提醒我们一点,在 onCreate 方法中只适合做些一些初始化设置工作,使用 View 执行动画或者其他操作时,一定要注意考虑 View 绘制的耗时过程。

那么问题来了,怎么样才能在 Activity 代码中获取到 View 的实际宽高值呢?这里给大家总结一些常用方法。

addOnGlobalLayoutListener

view.getViewTreeObserver().addOnGlobalLayoutListener(
        new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override 
    public void onGlobalLayout() { 
        if (Build.VERSION.SDK_INT >= 16) { 
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this); 
        } else { 
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
        } 
        int width = view.getWidth(); 
    }
});

ViewTreeObserver,顾名思义,视图树的观察者,可以监听 View 的全局变化事件。比如,layout 变化,draw 事件等。可以阅读源码了解更多事件。

注意:使用时需要注意及时移除该事件的监听,避免后续每一次发生全局 View 变化均触发该事件,影响性能。这里用的是 OnGlobalLayoutListener,移除监听时注意 API 的版本兼容处理。

addOnPreDrawListener

view.getViewTreeObserver().addOnPreDrawListener(
            new ViewTreeObserver.OnPreDrawListener() { 
    @Override 
    public boolean onPreDraw() { 
        view.getViewTreeObserver().removeOnPreDrawListener(this); 
        int width = view.getWidth(); return false; 
    }
});

这里同样是利用 ViewTreeObserver 观察者类,只不过这里监听的是 draw 事件。

view.post()

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

利用 Handler 通信机制,添加一个 Runnable 到 message queue 中,当 view layout 处理完成时,自动发送消息,通知 UI 线程。借此机制,巧妙获取 View 的宽高属性。代码简洁,使用简单,相比 ViewTreeObserver 监听处理,还不需要手动移除观察者监听事件。

onLayout()

view = new View(this) { 
    @Override 
    protected void onLayout(boolean changed, int l, int t, int r, int b) { 
        super.onLayout(changed, l, t, r, b); 
        int = view.getWidth(); 
    }
};

利用 View 绘制过程中的 onLayout() 方法获取宽高值,这也是为一个不需要借助其他类或者方法,仅靠自己就能完成获取宽高属性的手段。但是局限性在于,在 Activity 中使用代码创建 View 的场景较少,一般都是获取 layout 文件所加载 View 的宽高。

addOnLayoutChangeListener

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 
    @Override 
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 
        view.removeOnLayoutChangeListener(this); 
        int width = view.getWidth(); 
    }
});

监听 View 的 onLayout() 绘制过程,一旦 layout 触发变化,立即回调 onLayoutChange
方法。注意,用完也要注意调用 remove 方法移除监听事件。

ViewCompat.isLaidOut(view)

if (ViewCompat.isLaidOut(view)) { 
    int width = view.getWidth();
}

严格意义上来讲,这不能作为一个获取宽高的方式之一。充其量只能是一个判断条件。只有当 View 至少经历过一次 layout 时,isLaidOut() 方法才能返回 true,继而才能获取到 View 的真实宽高。所以,当我们的代码中有多次调用获取宽高时,才有可能使用这个方法判断处理。

getMeasuredWidth()

最后,借此地儿补充一点知识,getMeasuredWidth() 与 getWidth() 或者 getMeasuredHeight() 与 getHeight() 的区别。很多人容易对此产生混淆,不知道这两个方法到底有什么区别,使用时应该如何取舍。其实,官方文档介绍 View class 时,对于 Size 部分,有这么一段话:

The size of a view is expressed with a width and a height. A view actually possess two pairs of width and height values.
The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().
The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout. These values may, but do not have to, be different from the measured width and height. The width and height can be obtained by calling getWidth() and getHeight().

这段话足以解释 getMeasuredXXX() 与 getXXX() 的区别和联系所在。说得直白一点,measuredWidth 与 width 分别对应于视图绘制 的 measure 与 layout 阶段。很重要的一点是,我们要明白,View 的宽高是由 View 本身和 parent 容器共同决定的,要知道有这个 MeasureSpec 类的存在。

比如,View 通过自身 measure() 方法向 parent 请求 100x100 的宽高,那么这个宽高就是 measuredWidth 和 measuredHeight 值。但是,在 parent 的 onLayout() 阶段,通过 childview.layout() 方法只分配给 childview 50x50 的宽高。那么,这个 50x50 宽高就是 childview 实际绘制并显示到屏幕的宽高,也就是 width 和 height 值。

如果你对自定义 View 过程很熟练的话,理解这部分内容就比较轻松一些。事实上,开发过程中,getWidth() 和 getHeight() 方法用的更多一些。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容