android view 原理 -- measure 分析与应用
来源:互联网 发布:按键精灵淘宝秒杀脚本 编辑:程序博客网 时间:2024/06/06 02:06
1 概述
measure方法,主要是用于测量android中view的大小,为后面的layout做好准备,这里我们主要来看measure的流程。
2 分析
查看view中的方法,
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
这个方法是测量方法,但是这里这个方法是final的,也就是说无法重写,其实这里面最终是调用onMeasure来完成测量。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
android的视图测量就是从上到下遍历的测量,他的流程如下:
比较简单,值得注意的是measure方法中有一个widthMeasureSpec值,这个值是一个int,它的高两位代表了specMode,低30位代表了specsize,可以使用MeasureSpec来打包和解包这个值,从而获得mode和size。
先来看看MeasureSpec的mode:
UNSPECIFIED//不做限制EXACTLY//指定精确值,在match_parent和具体数值时,适用与这种场景AT_MOST//指定最大值,往往对应于layoutParams中的wrap_content
一个普通view的mode如何来确立呢,我们查看android中viewGroup的源码中的getChildMeasureSpec方法,如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
这里的代码其实比较简单,总的来说,从参数来看,需要的参数是当前viewGroup的MeasureSpec以及padding,还有子view的大小,在里面判断的时候,还会结合子view的layoutParams。这里可以把上面的代码总结成一个表格:
可以看到,parent和子view的关系。
3 应用
(1) 自定义控件实现wrap_content
在自定义控件的时候,如果不重写onMeasure方法,往往wrap_content配置不起作用,这是为什么呢。我们先来看看View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
这里面调用了setMeasuredDimension方法来设置最终的大小,所以这里面决定大小的实际上是getDefaultSize方法,看看这个方法的实现:
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
这里我们先不管参数size,在AT_MOST和EXACTLY这两种模式下用不到,我们结合前面讲解的getChildMeasureSpec方法以及表格可以知道,当前view的MeasureSpec是根据parent和当前view的layoutParams决定的,例如wrap_content,这个在上面的表格中有所体现,可以看到,最终wrap_content和match_parent和parent的mode结合后,就会产生AT_MOST和EXACTLY这两种模式,且size都是parent的size,而上面的方法中,这两种模式的处理方式都是直接返回specSize,由于size是parent的size,也就导致了其实wrap_content和match_parent一致。
所以我们需要重写onMeasure方法来实现wrap_content
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { //宽高都是wrap_content setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { //宽是wrap_content setMeasuredDimension(mWidth, heightMeasureSpec); } else if (heightMeasureSpec == MeasureSpec.AT_MOST) { //高是wrap_content setMeasuredDimension(widthMeasureSpec, mHeight); } }
这里的mWidth和mHeight是自己根据情况自定的,以便实现wrap_content效果。
(2) 获取view宽高
要获取view的宽高,必须等待view测量完毕后才能得到正确的大小,那么如何保证获取的时候已经测量完毕,这里一共有四种方法
(a)onWindowFocusChanged
这个方法activity和view都有,在焦点发生变化的时候,就会回调这个方法。
@Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (hasFocus()) { //获取大小 } }
(b)view.post(runnable)
我们知道,android中view的事件,包括touch,测量等等,都是通过发送消息到ui线程来执行的,那么使用这个方法,可以将我们获取大小的方法加入到队列末端。从而保证在测量完成后执行。
post(new Runnable() { @Override public void run() { //获取大小 } })
(c)ViewTreeObserver
这个类可以是一个view树的观察者,当它内部的view发生变化的时候就会回调其中响应的方法,这里使用OnGlobalLayoutListener,当view的layout状态发生变化的时候,就会回调。由于layout是发生在onmeasure之后的,所以保证了测量的完成。由于这个方法可能多次回调,所以在回调中,需要注销监听。
ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); //获取大小 } });
(d)view.measure(int widthMeasureSpec, int heightMeasureSpec)
这种方法是直接调用view的measure方法来手动测量,但是这里要根据不同的layoutParams来做不同的方法。
match_parent
这种情况没办法测量,因为这里并不知道parent的大小。
具体数值
指定了具体数值,那么根据前面的表格,可以看到,mode就是EXACTLY,所以可以使用如下代码:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); view.measure(widthMeasureSpec,heightMeasureSpec);
这里的width和height就是指定的具体宽高。
wrap_content
同样的,根据前面的表格,这里的模式是AT_MOST,所以使用如下代码:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST); int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST); view.measure(widthMeasureSpec,heightMeasureSpec);
前面也讲到了,这里的widthMeasureSpec高两位是mode,低30是大小。所以必须左移30后减1,从而得到最大的值。
这篇文章介绍了measure的流程,以及应用,就到这里。
- android view 原理 -- measure 分析与应用
- 【Android】View绘制过程分析之measure
- Android View measure (一) 流程分析
- Android获取View的宽高与View.measure详解
- Android获取View的宽高与View.measure详解
- android View.measure
- Android View measure过程
- android View.measure()初探
- Android View的Measure
- Android View-measure
- Android中View的绘制原理之measure
- Android View的工作流程总结分析(二)-Measure
- Android 自定义View measure模板
- Android View measure流程详解
- Android自定义view之Measure
- View的工作原理(1)--Measure
- View工作原理(measure、layout、draw)
- View的工作原理:measure、layout、draw
- Thrift使用指南
- CodeForces - 366A Dima and Guards (模拟)
- android studio 的 Butter Knife:8.0.1的完整正确导入方法
- 浅谈Thrift内部实现原理
- 6. ZigZag Conversion
- android view 原理 -- measure 分析与应用
- c++实验6
- C++的一些简单示例
- C++学习笔记:转换构造函数与类型转换函数
- C# 压缩和解压文件
- 代码混淆 proguard相关配置过程记录
- 面试
- BP神经网络
- MyEclipse2015 Stable2.0 安装与破解