Android自定义View:MeasureSpec的真正意义与View大小控制

简介: Android自定义View:MeasureSpec的真正意义与View大小控制
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);
}

可以看到父控件会参考自己的MeasureSpec跟子View的布局参数,为子View构建合适的MeasureSpec,盗用网上的一张图来描述就是

当子View接收到父控件传递的MeasureSpec的时候,就可以知道父控件希望自己如何显示,这个点对于开发者而言就是onMeasure函数,先来看下View.java中onMeasure函数的实现:

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

其中getSuggestedMinimumWidth是根据设置的背景跟最小尺寸得到一个备用的参考尺寸,接着看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;
}

可以看到,如果自定义View没有重写onMeasure函数,MeasureSpec.AT_MOST跟MeasureSpec.AT_MOST的表现是一样的,也就是对于场景2跟3的表现其实是一样的,也就是wrap_content就跟match_parent一个效果,现在我们知道MeasureSpec的主要作用:父控件传递给子View的参考,那么子View拿到后该如何用呢?

自定义View尺寸的确定

============

接收到父控件传递的MeasureSpec后,View应该如何用来处理自己的尺寸呢?onMeasure是View测量尺寸最合理的时机,如果View不是ViewGroup相对就比较简单,只需要参照MeasureSpec,并跟自身需求来设定尺寸即可,默认onMeasure的就是完全按照父控件传递MeasureSpec设定自己的尺寸的。这里重点讲一下ViewGroup,为了获得合理的宽高尺寸,ViewGroup在计算自己尺寸的时候,必须预先知道所有子View的尺寸,举个例子,用一个常用的流式布局FlowLayout来讲解一下如何合理的设定自己的尺寸。

先分析一下FLowLayout流式布局(从左到右)的特点:FLowLayout将所有子View从左往右依次放置,如果当前行,放不开的就换行。从流失布局的特点来看,在确定FLowLayout尺寸的时候,我们需要知道下列信息,

  • 父容器传递给FlowLayout的MeasureSpec推荐的大小(超出了,显示不出来,又没意义)
  • FlowLayout中所有子View的宽度与宽度:计算宽度跟高度的时候需要用的到。
  • 综合MeasureSpec跟自身需求,得出合理的尺寸

首先看父容器传递给FlowLayout的MeasureSpec,对开发者而言,它可见于onMeasure函数,是通过onMeasure的参数传递进来的,它的意义上面的已经说过了,现在来看,怎么用比较合理?其实ViewGroup.java源码中也提供了比较简洁的方法,有两个比较常用的measureChildren跟resolveSize,在之前的分析中我们知道measureChildren会调用getChildMeasureSpec为子View创建MeasureSpec,并通过measureChild测量每个子View的尺寸。那么resolveSize呢,看下面源码,resolveSize(int size, int measureSpec)的两个输入参数,第一个参数:size,是View自身希望获取的尺寸,第二参数:measureSpec,其实父控件传递给View,推荐View获取的尺寸,resolveSize就是综合考量两个参数,最后给一个建议的尺寸:

public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
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);
}

可以看到:

  • 如果父控件传递给的MeasureSpec的mode是MeasureSpec.UNSPECIFIED,就说明,父控件对自己没有任何限制,那么尺寸就选择自己需要的尺寸size
  • 如果父控件传递给的MeasureSpec的mode是MeasureSpec.EXACTLY,就说明父控件有明确的要求,希望自己能用measureSpec中的尺寸,这时就推荐使用MeasureSpec.getSize(measureSpec)
  • 如果父控件传递给的MeasureSpec的mode是MeasureSpec.AT_MOST,就说明父控件希望自己不要超出MeasureSpec.getSize(measureSpec),如果超出了,就选择MeasureSpec.getSize(measureSpec),否则用自己想要的尺寸就行了

对于FlowLayout,可以假设每个子View都可以充满FlowLayout,因此,可以直接用measureChildren测量所有的子View的尺寸:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int paddingTop = getPaddingTop();
int count = getChildCount();
int maxWidth = 0;
int totalHeight = 0;
int lineWidth = 0;
int lineHeight = 0;
int extraWidth = widthSize - paddingLeft - paddingRight;
measureChildren(widthMeasureSpec, heightMeasureSpec);
for (int i = 0; i < count; i++) {
View view = getChildAt(i);
if (view != null && view.getVisibility() != GONE) {
if (lineWidth + view.getMeasuredWidth() > extraWidth) {
totalHeight += lineHeight ;
lineWidth = view.getMeasuredWidth();
lineHeight = view.getMeasuredHeight();
maxWidth = widthSize;
} else {
lineWidth += view.getMeasuredWidth();
}
相关文章
|
1天前
|
存储 数据库 Android开发
Android中的View显示过程
Android中的View显示过程
8 1
|
12天前
|
XML API Android开发
android上FragmentTabHost实现自定义Tab Indicator
android上FragmentTabHost实现自定义Tab Indicator
11 1
|
12天前
|
XML 前端开发 API
Android中实现Bitmap在自定义View中的放大与拖动
Android中实现Bitmap在自定义View中的放大与拖动
24 1
|
12天前
|
XML IDE 开发工具
【Android UI】自定义带按钮的标题栏
【Android UI】自定义带按钮的标题栏
21 7
【Android UI】自定义带按钮的标题栏
|
16天前
|
XML API Android开发
06. 【Android教程】Android 的 UI 根基 View与View
06. 【Android教程】Android 的 UI 根基 View与View
9 1
|
27天前
|
搜索推荐 Android开发 开发者
Android应用开发:实现自定义ViewPager2的完全指南
【5月更文挑战第23天】 在移动应用开发的领域中,为用户提供流畅且直观的界面体验至关重要。Android平台上的ViewPager2组件提供了一个强大的方式来实现可滑动的页面,但有时候默认的行为和样式可能无法满足特定的设计要求。本文将深入探讨如何通过创建一个自定义的ViewPager2来扩展其功能,包括实现新的转换效果、修改指示器样式以及增加触摸事件响应等。我们将提供详尽的步骤和代码示例,帮助开发者掌握创建高度定制的ViewPager2所需的关键概念和技术。
|
1月前
|
Android开发 异构计算 前端开发
Android显示原理,安卓自定义view面试
Android显示原理,安卓自定义view面试
|
Android开发 开发者 容器
Android自定义View:MeasureSpec的真正意义与View大小控制
Android自定义View:MeasureSpec的真正意义与View大小控制
225 0
Android自定义View:MeasureSpec的真正意义与View大小控制
|
1天前
|
人工智能 API 语音技术
使用 Gemini Pro AI 开发 Android 应用程序
使用 Gemini Pro AI 开发 Android 应用程序
5 0
|
1天前
|
Dart IDE Linux
开发Android应用程序
开发Android应用程序
5 1