阅读android源代码,发现几个函数(例如getLeft(),getMeasuredWidth(),getPaddingLeft(),getMeasuredWidth()
)的关系没有搞明白,今天阅读View文档,重点记录如下,以备将来查阅
Position
The geometry of a view is that of a rectangle. A view has a location, expressed as a pair of left and top coordinates, and two dimensions, expressed as a width and a height. The unit for location and dimensions is the pixel.
视图的几何形状是矩形的。视图有一个位置,表示为一对左和顶坐标,以及二维,表示为宽度和高度。位置和尺寸的单位是像素。
It is possible to retrieve the location of a view by invoking the methods getLeft()
and getTop()
. The former returns the left, or X, coordinate of the rectangle representing the view. The latter returns the top, or Y, coordinate of the rectangle representing the view. These methods both return the location of the view relative to its parent. For instance, when getLeft() returns 20, that means the view is located 20 pixels to the right of the left edge of its direct parent.
可以通过调用getLeft()和getTop()方法检索视图的位置。前者返回表示视图的矩形的左坐标或X坐标。后者返回表示视图的矩形的顶部或Y坐标。这些方法都返回视图相对于其父视图的位置。例如,当getLeft()返回20时,这意味着视图位于其直接父级左侧边缘的右侧20像素。
In addition, several convenience methods are offered to avoid unnecessary computations, namely getRight()
and getBottom()
. These methods return the coordinates of the right and bottom edges of the rectangle representing the view. For instance, calling getRight()
is similar to the following computation: getLeft() + getWidth()
(see Sizefor more information about the width.)
此外,还提供了几种方便的方法来避免不必要的计算,即getRight()和getBottom()。这些方法返回表示视图的矩形的右下角的坐标。例如,调用getRight()与以下计算类似:getLeft()getWidth()(有关宽度的更多信息,请参见Size)。
Size, padding and margins
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()
.
第一对被称为测量的宽度和测量的高度。这些维度定义了视图希望在其父视图中的大小(有关更多细节,请参见布局)。所测量的尺寸可以通过调用getAuthuredWidth()和getAuthuredHL.8()来获得。
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()
.
第二对简单地称为宽度和高度,或有时绘图宽度和绘图高度。这些维度定义屏幕上、绘图时间和布局后视图的实际大小。这些值可能(但不必)与测量的宽度和高度不同。可以通过调用getWidth()和getHL.8()来获得宽度和高度。
To measure its dimensions, a view takes into account its padding. The padding is expressed in pixels for the left, top, right and bottom parts of the view. Padding can be used to offset the content of the view by a specific amount of pixels. For instance, a left padding of 2 will push the view's content by 2 pixels to the right of the left edge. Padding can be set using the setPadding(int, int, int, int)
or setPaddingRelative(int, int, int, int)
method and queried by calling getPaddingLeft()
, getPaddingTop()
, getPaddingRight()
, getPaddingBottom()
, getPaddingStart()
, getPaddingEnd()
.
为了测量它的尺寸,视图会考虑到它的填充。填充以像素表示,用于视图的左、上、右和底部部分。填充可用于将视图的内容偏移特定数量的像素。例如,左填充2将视图的内容推到左侧边缘的右侧2个像素。可以使用setPadd(int,int)或setPaddingRelative(int,int)方法设置填充,并通过调用getPaddingLeft()、getPaddingTop()、getPaddingRight()、getPaddingBottom()、getPaddingStart()、getPaddingEnd()查询填充。
Even though a view can define a padding, it does not provide any support for margins. However, view groups provide such a support. Refer to ViewGroup
and ViewGroup.MarginLayoutParams
for further information.
即使视图可以定义填充,它也不提供对边距的任何支持。但是,视图组提供了这样的支持。有关更多信息,请参阅ViewGroup和ViewGroup.MarginLayoutParams。
Layout
Layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int)
and is a top-down traversal of the view tree. Each view pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every view has stored its measurements. The second pass happens inlayout(int, int, int, int)
and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.
布局是两个传递过程:度量传递和布局传递。度量传递是在 measure
(int,int)中实现的,是视图树的自顶向下遍历。在递归过程中,每个视图都将维度规范推下树。在度量值传递结束时,每个视图都存储其测量值。第二次传递发生在layout
(int,int)中,并且也是自顶向下的.在此传递期间,每个父级负责使用度量值传递中计算的大小来定位其所有子节点。
When a view's measure() method returns, its getMeasuredWidth()
and getMeasuredHeight()
values must be set, along with those for all of that view's descendants. A view's measured width and measured height values must respect the constraints imposed by the view's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent view may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small.
当视图的measure()方法返回时,必须设置其getMeasuredWidth()和getMeasuredHeight()值,以及该视图的所有子View的值。 视图的测量宽度和测量高度值必须遵守视图父项施加的约束。 这保证了在测量通过结束时,所有父View都接受他们子View的所有测量。 父视图可以在其子项上多次调用measure()。 例如,父View可以用未指定的维度测量每个子View一次以找出他们想要的大小,然后如果所有子View的无约束大小的总和太大或太小,则用实际数字再次对他们调用measure()。
The measure pass uses two classes to communicate dimensions. The MeasureSpec
class is used by views to tell their parents how they want to be measured and positioned. The base LayoutParams class just describes how big the view wants to be for both width and height. For each dimension, it can specify one of:
度量过程使用两个类来传达维度。 视图使用MeasureSpec类告诉他们的父view他们想要如何测量和定位。 基本的LayoutParams类只描述了视图对宽度和高度的要求。 对于每个维度,它可以指定以下之一:
- an exact number
- MATCH_PARENT, which means the view wants to be as big as its parent (minus padding)
- WRAP_CONTENT, which means that the view wants to be just big enough to enclose its content (plus padding).
There are subclasses of LayoutParams for different subclasses of ViewGroup. For example, AbsoluteLayout has its own subclass of LayoutParams which adds an X and Y value.
LayoutGroup的不同子类有LayoutParams的子类。 例如,AbsoluteLayout有自己的LayoutParams子类,它添加了X和Y值。
MeasureSpecs are used to push requirements down the tree from parent to child. A MeasureSpec can be in one of three modes:
- UNSPECIFIED: This is used by a parent to determine the desired dimension of a child view. For example, a LinearLayout may call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child view wants to be given a width of 240 pixels.
未指明的:父母使用它来确定子视图的所需维度。 例如,LinearLayout可以在其子节点上调用measure(),其高度设置为UNSPECIFIED,宽度为240,以找出子视图在宽度为240像素时的高度。
- EXACTLY: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.
- 确切地:父View使用它来对子View施加确切的大小。 子View必须使用这个尺寸,并保证其所有后代都符合这个尺寸。
- AT_MOST: This is used by the parent to impose a maximum size on the child. The child must guarantee that it and all of its descendants will fit within this size.
最多:父母用它来强加孩子的最大尺寸。 孩子必须保证它和它的所有后代都适合这个尺寸。
To initiate a layout, call requestLayout()
. This method is typically called by a view on itself when it believes that is can no longer fit within its current bounds.
要启动布局,请调用requestLayout()。 当该方法认为它不再适合其当前边界时,该方法通常由视图自身调用。
getMeasuredWidth()是实际View的大小,与屏幕无关;
View的getWidth()和getMeasuredWidth()有什么区别吗?
View的高宽是由View本身和Parent容器共同决定的。getMeasuredWidth()和getWidth()分别对应于视图绘制的measure和layout阶段。getMeasuredWidth()获取的是View原始的大小,也就是这个View在XML文件中配置或者是代码中设置的大小;getWidth()获取的是这个View最终显示的大小,这个大小有可能等于原始的大小,也有可能不相等。比如说,在父布局的onLayout()或者该View的onDraw()方法里调用measure(0, 0),二者的结果可能会不同(measure中的参数可以自己定义)。
getWidth()
/**
* Return the width of the your view.
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
从源码上看,getWidth()是根据mRight和mLeft之间的差值计算出来的,需要在布局之后才能确定它们的坐标,也就是说布局后在onLayout()方法里才能调用getWidth()来获取。因此,getWidth()获取的宽度是在View设定好布局后整个View的宽度。
那么问题来了,mRight和mLeft是什么值,是在什么时候被设置的。我们再看layout阶段的源码:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft; // mLeft全局变量
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
}
在layout阶段会去调用setOpticalFrame()或者调用setFrame()方法,从源码中可知setOpticalFrame()方法,最终还是调用的setFrame()方法。
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
}
所以最终的mLeft和mRight的值是在setFrame()方法中被设置的。而且这些mLeft,mRight代表了view最终显示在界面中的大小。
getMeasuredWidth()
/**
* Like {@link #getMeasuredWidthAndState()}, but only returns the
* raw width component (that is the result is masked by
* {@link #MEASURED_SIZE_MASK}).
* @return The raw measured width of this view.
*/
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
从源码上看,getMeasuredWidth()获取的是mMeasuredWidth的这个值。这个值是一个8位的十六进制的数字,高两位表示的是这个measure阶段的Mode的值,具体可以查看MeasureSpec的原理。这里mMeasuredWidth & MEASURED_SIZE_MASK表示的是测量阶段结束之后,View真实的值。而且这个值会在调用了setMeasuredDimensionRaw()函数之后会被设置。所以getMeasuredWidth()的值是measure阶段结束之后得到的View的原始的值。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
总结一下,getMeasuredWidth是measure阶段获得的View的原始宽度,getWidth是layout阶段完成后,其在父容器中所占的最终宽度
如何在onCreate中拿到View的宽度和高度?
view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth();
int measuredWidth = view.getMeasuredWidth();
Log.i(TAG, "width: " + width);
Log.i(TAG, "measuredWidth: " + measuredWidth);
}
});
利用Handler通信机制,发送一个Runnable到MessageQueue中,当View布局处理完成时,自动发送消息,通知UI进程。借此机制,巧妙获取View的高宽属性,代码简洁,相比ViewTreeObserver监听处理,还不需要手动移除观察者监听事件。
因为UI线程消息队列会按顺序处理事件,在setContentView()被调用后,消息队列中会包含一个要求重新Layout的msg,所以任何你post到UI线程队列中的东西都会在Layout后执行。
- ViewTreeObserver.addOnGlobalLayoutListener()
监听View的onLayout()绘制过程,一旦layout触发变化,立即回调onLayoutChange方法。
注意,使用完也要注意调用removeOnGlobalListener()方法移除监听事件。避免后续每一次发生全局View变化均触发该事件,影响性能。
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Log.i(TAG, "width: " + view.getWidth());
Log.i(TAG, "height: " + view.getHeight());
}
});
3 View.measure(int widthMeasureSpec, int heightMeasureSpec)
除了在onCreate()中获得View的高宽,还可以在Activity的onWindowFocusChanged() 方法中获得高宽。
getIntrinsicWidth()
是原有宽度,有时候原有宽度可能很大,但是实际上空间不够,所有效果上并没有那么大,这个方法可以获得原有宽度,可以辅助测量的时候选择合适的展示宽度。
getMinimumWidth()
是最小宽度,是XML参数定义里的 minWidth,也是一个辅助测量展示的参数。
getPaddingLeft()
getLeft(), getRight(), getTop(), getBottom()