view的工作原理
DecorView和ViewRoot
ViewRoot对应ViewRootImpl,他是链接windowsmanager和DecorView的枢纽
viewroot控制view的三大流程
流程如图所示
DecorView中包含一个标题栏和一个内容栏
内容栏的id为content,在setcontentview的时候设置view进去
MeasureSpec
MeasureSpec.getSize(heightMeasureSpec);
MeasureSpec.getMode(heightMeasureSpec);
获取测量模式
测量模式有三种
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
AT_MOST对应wrap_content
EXACTLY对应具体的数值或者match_parent
UNSPECIFIED一般为系统内部使用
如果测量模式是AT_MOST
需要手动计算自定义的宽高,比如自定义view是由俩个圆形组成,横向排列
那么宽为俩个圆的半径+左边距+右边距
那么高为一个圆的半径+上边距+下边距
为什么测量模式是AT_MOST要特殊处理?
通过view的源码分析得知
下面是view的onMeasure源码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}
从源码中可以看到测量模式AT_MOST,EXACTLY的处理结果是一样的,所以如果自定义view你使用wrap_content,他的效果和match_parent是一样的
view的测量模式规则如下表
继续分析view的onMeasure过程源码,上面的俩个方法
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
这个最小宽高可以在xml文件中设置,如果view设置了背景,就返回max(mMinWidth, mBackground.getMinimumWidth());他们的最大值
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
getMinimumWidth方法返回的就是drawable的原始宽度
比如bitmapdrawable就还有原始宽度
继续分析viewGroup的onMeasure过程
viewgroup没有重写view的onMeasure方法
而是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);
}
}
}
viewgroup会遍历自己的子view,然后继续调用他们的measure方法
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);
}
viewgroup是个抽象类,没有具体的onmeasure实现过程。所以我们找一个他的实现类来分析,比如linearlayout
@Override
protected 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 (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
}
}
可以看到linearlayout也会遍历自己的子元素,然后调用measureChildBeforeLayout
这个方法就是调用子元素调用他们的onmeasure方法
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
linearlayout在测量完子view之后,根据子view的宽高得到自己的宽高
在onmeasure之后。就能通过getMeasureHeight获得view的宽高,但是这个获取的时机是需要注意的,它与activity的生命周期并没有很直接的关系,一般建议在以下几种地方能获取到
1,onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus){
//获取宽高
}else {
}
}
2,onlayout
3,通过post一个runnable到消息队列,
getRootView().post(new Runnable() {
@Override
public void run() {
//获取宽高
}
})
4,通过viewtree
ViewTreeObserver observer=view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//获取宽高
}
});
接下来是三大流程之onlayout
//未完待续,持续更新