一.View的measure过程

View的measure过程是由View的measure方法完成的,他是一个被final关键字修饰的方法,我们无法重写该方法,但是measure方法中会调用onMeasure方法来设置计算后的宽高,onMeasure方法是可以被重写的:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

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;}

可以看到EXACTLY和AT_MOST两种模式下都是以specSize作为返回值,而这个specSize就是View测量后的大小。如果View采用AT_MOST模式即wrap_content来绘制那么结合上一篇文章中的图例:

可以知道View最终在父布局中的绘制会以parentSize作为specSize的实际大小,即我们自定义的直接继承自View的View在布局中使用wrap_content的效果和match_parent是一样的,而解决这个问题的方式就需要重写onMeasure方法来对wrap_content做特殊处理

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(mWidth, mHeight)} else if (widthSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(mWidth, heightSpecSize)} else if (heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSpecSize, mHeight)}}

重写的onMeasure方法中我们需要提供一个View在wrap_content情况下使用的宽高mWidth和mHeight,非wrap_content的场景下就直接使用系统提供的测量值widthSpecSize/heightSpecSize即可。具体mWidth和mHeight该怎么取值要根据实际使用场景来定,参考TextView的onMeasure方法部分源码:

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int width;int height;...if (widthMode == MeasureSpec.EXACTLY) {// Parent has told us how big to be. So be it.width = widthSize;} else {//AT_MOST...if (boring == null || boring == UNKNOWN_BORING) {if (des < 0) {des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,mTransformed.length(), mTextPaint, mTextDir, widthLimit));}width = des;} else {width = boring.width;}final Drawables dr = mDrawables;if (dr != null) {width = Math.max(width, dr.mDrawableWidthTop);width = Math.max(width, dr.mDrawableWidthBottom);}...}...if (heightMode == MeasureSpec.EXACTLY) {// Parent has told us how big to be. So be it.height = heightSize;mDesiredHeightAtMeasure = -1;} else {//AT_MOSTint desired = getDesiredHeight();height = desired;mDesiredHeightAtMeasure = desired;if (heightMode == MeasureSpec.AT_MOST) {height = Math.min(desired, heightSize);}}...setMeasuredDimension(width, height);}

可以看到TextView对AT_MOST模式下的宽高都进行了重新定义,具体赋值逻辑太过复杂就不细说了。

二.ViewGroup的measure过程

ViewGroup是一个继承自View的抽象类,它并没有实现onMeasure方法,这是因为ViewGroup作为一个父类他不能对不同需求和场景下的子类布局作统一测量,就像LinearLayout和RelativeLayout一样是两种完全不同的布局方式,他们的测量方式需要他们自己去实现。但是ViewGroup也提供了一个measureChildren方法:

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {final int size = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < size; ++i) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {measureChild(child, widthMeasureSpec, heightMeasureSpec);}}}

逻辑上很简单,就是按顺序调用measureChild方法来测量子View:

    protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

measureChild方法和上一篇文章提到的measureChildWithMargins方法原理是一样的,只不过没有把子View的外边距加进去。不过这个measureChildren方法在已知的几种布局中只在AbsoluteLayout布局中有使用,而AbsoluteLayout作为最简单粗暴的一种布局也是几乎没有使用场景,所以可以看出对于子View的测量LinearLayout、RelativeLayout等常用布局都是需要自己去实现的。下面以LinearLayout的onMeasure方法为例:

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}}

measureVertical方法:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {...for (int i = 0; i < count; ++i) {final View child = getVirtualChildAt(i);if (child == null) {mTotalLength += measureNullChild(i);continue;}...if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {// Optimization: don't bother measuring children who are only// laid out using excess space. These views will get measured// later if we have space to distribute.final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);skippedMeasure = true;} else {if (useExcessSpace) {// The heightMode is either UNSPECIFIED or AT_MOST, and// this child is only laid out using excess space. Measure// using WRAP_CONTENT so that we can find out the view's// optimal height. We'll restore the original height of 0// after measurement.lp.height = LayoutParams.WRAP_CONTENT;}// Determine how big this child would like to be. If this or// previous children have given a weight, then we allow it to// use all available space (and we will shrink things later// if needed).final int usedHeight = totalWeight == 0 ? mTotalLength : 0;measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);final int childHeight = child.getMeasuredHeight();if (useExcessSpace) {// Restore the original height and record how much space// we've allocated to excess-only children so that we can// match the behavior of EXACTLY measurement.lp.height = 0;consumedExcessSpace += childHeight;}final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));...}}
}

可以看到LinearLayout会按序遍历每一个子View,并调用measureChildBeforeLayout方法来测量子View,测量完成后获取子View的measuredHeight累加到mTotalLength中作为最后LinearLayout的总高度。所有的子View都测量完成后,LinearLayout会用mTotalLength来测量自身的高度:

mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);

这里的resolveSizeAndState方法就是对不同测量模式下的LinearLayout高度分情况处理:

    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {final int specMode = MeasureSpec.getMode(measureSpec);final int specSize = MeasureSpec.getSize(measureSpec);final int result;switch (specMode) {case MeasureSpec.AT_MOST:if (specSize < size) {result = specSize | MEASURED_STATE_TOO_SMALL;} else {result = size;}break;case MeasureSpec.EXACTLY:result = specSize;break;case MeasureSpec.UNSPECIFIED:default:result = size;}return result | (childMeasuredState & MEASURED_STATE_MASK);}

当LinearLayout采用match_parent时就直接使用测量的specSize,如果采用wrap_content就使用累加得到的总高度值mTotalHeight,当然这个值也要小于等于父布局给的剩余高度,否则仍然以specSize作为LinearLayout的最终高度。

三.获取measure后的宽高

对于获取measure后的宽高View直接给我们提供了getMeasuredWidth/getMeasuredHeight方法,但是应该在什么时候使用这俩方法呢,首先View在很多情况下会出现多次测量的情况,所以在onMeasure方法中获取的measuredWidth/measuredHeight往往并不是最终正确的宽高,而onLayout是在onMeasure完全结束的情况下执行的,所以一般我们会在onLayout方法中去拿到最终的measuredWidth/measuredHeight。

如果在activity的声明周期方法里面去getMeasuredWidth/getMeasuredHeight会得到正确的宽高吗?答案往往是否定的,因为整个measure的过程和页面的生命周期并没有绑定,当我们在onStart、onResume方法里面去获取宽高时可能View还没有measure结束而获取到一个默认值0。Android为我们提供了以下几种方式去拿到正确的measuredWidth/measuredHeight:

1.onWindowFocusChanged

class DemoView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {...override fun onWindowFocusChanged(hasWindowFocus: Boolean) {super.onWindowFocusChanged(hasWindowFocus)if (hasWindowFocus) {val width = this.measuredWidthval height = this.measuredHeight}}...}

这个回调在activity中也是可以设置的,但是需要注意的是它和activity的生命周期存在关联,可能会出现频繁回调的情况,当activity频繁的触发onResume和onPause时onWindowFocusChanged也会频繁的触发回调

2.view.post

View通过post方法把一个Runnable任务加到主线程消息队列的末尾,当这个Runnable执行时View早已经初始化好了:

btn1.post {val width = btn1.measuredWidthval height = btn1.measuredHeight
}

3.ViewTreeObserver

ViewTreeObserver提供了很多和视图树状态有关的接口,很多都是可以用来获取measuredWidth/measuredHeight,以OnGlobalLayoutListener为例:

class DemoView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {...    init {viewTreeObserver.addOnGlobalLayoutListener(object:ViewTreeObserver.OnGlobalLayoutListener{override fun onGlobalLayout() {viewTreeObserver.removeOnGlobalLayoutListener(this)val width = this@DemoView.measuredWidthval height = this@DemoView.measuredHeight}})}...
}

当view树的状态改变或者里面的view可见性发生变化都会触发OnGlobalLayoutListener回调,此时measuredWidth/measuredHeight将是准确的。

4.measure(int widthMeasureSpec, int heightMeasureSpec)

通过手动对View进行measure来指定width和height,但是这里需要根据LayoutParams来分情况处理:

.match_parent

这种情况下无法直接手动measure,因为我们需要知道父布局的剩余空间大小parentSize,而在当前View中我们是无法知道父布局的剩余空间大小情况的

.具体数值dp/px

例如宽高都是100px:

class DemoView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {...    init {val widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)val heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)this.measure(widthMeasureSpec, heightMeasureSpec)}...
}

.wrap_content

class DemoView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {...    init {val widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 shl 30) - 1, MeasureSpec.AT_MOST)val heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 shl 30) - 1, MeasureSpec.AT_MOST)this.measure(widthMeasureSpec, heightMeasureSpec)}...
}

当使用AT_MOST模式时,我们可以指定specSize为其所能达到的最大值即measureSpec表示具体尺寸的后三十位全为1,所以specSize=1*10的30次方-1,用kotlin的代码表示就是(1 shl 30) - 1,java的代码表示就是(1 << 30) - 1。

Android View的工作流程(二) measure过程相关推荐

  1. 【Android View绘制之旅】Measure过程

    1.为什么要进行Measure? 替人做了原本应该做的工作.在写xml的时候,布局参数如 wrap_content,match_parent,weight 等等给我们开发界面的时候带来方便,但是机器可 ...

  2. 【Android View绘制之旅】Draw过程

    出效果:绘制 经过前面的准备工作 :[Android View绘制之旅]Measure过程,[Android View绘制之旅]Layout过程 我们的视图具备了宽高数据,位置数据,现在到了激动人心的 ...

  3. android的构成和工作流程,分析Android中View的工作流程

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 在分析View的工作流程时,需要先分析一个很重要的类,MeasureSpec.这个类在View的测量(Measure)过 ...

  4. Android之wifi工作流程

    Android Wifi的工作流程 一.WIFI工作相关部分 Wifi 网卡状态 1.    WIFI_STATE_DISABLED:WIFI网卡不可用 2.    WIFI_STATE_DISABL ...

  5. Android O: View的绘制流程(二):测量

    在前一篇博客Android O: View的绘制流程(一): 创建和加载中,  我们分析了系统创建和加载View的过程,这部分内容完成了View绘制的前置工作. 本文开始分析View的测量的流程. 一 ...

  6. Android View的绘制流程

    View的绘制和事件处理是两个重要的主题,上一篇<图解 Android事件分发机制>已经把事件的分发机制讲得比较详细了,这一篇是针对View的绘制,View的绘制如果你有所了解,基本分为m ...

  7. Android View的绘制流程简述 Android自定义View(一)

    1 Android的UI管理系统层级关系 如上图所示,这就是Android的UI管理系统的层级关系. 1.1 当一个应用启动的时候,会启动一个主Activity,然后Activity会创建出一个窗口系 ...

  8. Android View的绘制流程(1) -- 测量onMeasure

    鉴于是首篇讲解自定义view流程,之前也在网上搜了一些博主的博客看了看,都是大同小异,今天抽时间自己总结一下,分享一下自己的感悟,也算是一篇笔记. (本篇为开头篇,稍微讲述一下有关的东西) View的 ...

  9. 【Android View绘制之旅】Layout过程

    1.为什么要进行Layout? 在[Android View绘制之旅]View之测量Measure过程后,View我们得到View的宽高,但光只有宽高值是不足以反映视图的,更需要知道View所在的位置 ...

最新文章

  1. 零基础入门学习Python(20)-lambda表达式、filter()、map() BIF
  2. python绘制月亮_用python画月亮的代码是什么?
  3. c++ map iterator 获取key_JAVA | Map集合使用详解
  4. 测试一体机风扇分贝软件,9款小风扇深度横评,风力、噪音测试加拆解,告诉你谁最值得买...
  5. 一般判五年几年能出来_A股十年不涨的“元凶”被揪了出来,指数不该被冤枉...
  6. delphi和python和halcon_【《zw版·Halcon与delphi系列原创教程》Halcon图层与常用绘图函数...
  7. 开源icon、SVG、字体图标库收集
  8. windows 微信多开脚本
  9. mrc mcr 与 bic orr 含义及用法示例
  10. Linux权限的理解 | 粘滞位 |权限掩码 |文件类型
  11. 戴尔通过F12一次性引导菜单刷新BIOS
  12. 计算机组成原理字发生器,计算机组成原理实验2.7时序发生器赖晓铮剖析.ppt
  13. 与领导喝酒的18个应紧记的诀窍
  14. ji计算机内存不足怎么回事,Win7提示内存不足的原因及应对措施
  15. 谷歌收购摩托罗拉移动---前途将何去何从
  16. 公共基础知识和计算机相关知识了解农业常识,公共基础知识:农业为本
  17. ubuntu20下安装nginx插件geoip2查询ip信息
  18. 《爱情公寓》剧红角色红 演员总差一点点
  19. 第十六课:应用分发(基于AndroidStudio3.2)
  20. PICE(6):集群环境里多异类端点gRPC Streaming - Heterogeneous multi-endpoints gRPC streaming

热门文章

  1. 详解安装msdn 2015及其注意事项
  2. 关于EasyUI DataGrid行编辑时嵌入时间控件
  3. 监控器物检测object detection实战
  4. 【Linux】部署web项目
  5. openSUSE15.2收发@aliyun.com电子邮件之thunderbird篇
  6. julia应用于自动驾驶汽车、机器人、3D 打印、精准医疗、增强现实、基因组学、能源交易、机器学习、金融风控和太空任务设计等多个领域...
  7. 梳理学习Kotlin,Function的用法
  8. 微型计算机原理rcr和rol,微机原理和接口技术后习题答案解析
  9. 【学习资料】Rol Pooling、RolAlign和RolWarp
  10. Ackerman函数的实现算法