Android-高级-UI-进阶之路-(三)-理解-View-工作原理并带你入自定义-View-门

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

}
复制代码

我们在继续看下 getRootMeasureSpec 的源码实现,代码如下:

//ViewRootImpl.java
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
/**
其实就是 XML 布局中定义的 MATCH_PARENT
/
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
/

其实就是 XML 布局中定义的 WRAP_CONTENT
/
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
/

*其实就是 XML 布局中定义的 绝对 px
*/
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码

通过上面代码,DecorView 的 MeasureSpec 的产生过程就很明确了,具体其准守如下规则,根据它的 LayoutParams 中的宽高的参数来划分。

  • LayoutParams.MATCH_PARENT: 精确模式,大小就是窗口的大小;
  • LayoutParams.WRAP_CONTENT: 最大模式,大小不定,此模式一般是子 View 决定,但是不能超过父容器的大小;
  • XML 中宽高 px/dp 固定: 精确模式,大小为 LayoutParams 中指定的大小。

那么对于普通 View 也就是 XML 布局中的 View 是怎么测量的呢?我们先来看一下 ViewGroup 的 measureChildWithMargins 方法,因为 View 的 measure 过程就是由它给传递过来的,代码如下:

//ViewGroup.java
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//得到子元素的 MeasureSpec
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);
    //对子View 开始进行 measure
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    复制代码

上述方法首先会拿到子 View 的 LayoutParams 布局中定义的参数,然后根据父容器的 MeasureSpec 、 子元素的 LayoutParams 、子元素的 padding 等然后拿到对子元素的测量规格,可以看 getChildMeasureSpec 代码具体实现:

//ViewGroup.java
//padding:指父容器中已占用的大小
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;
//根据父容器的测量规格和 View 本身的 LayoutParams 来确定子元素的 MeasureSpec
switch (specMode) {

case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {

resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {

resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {

resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}

return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码

这一段代码虽然比较多,但是还是比较容易理解,它主要的工作就是先拿到在父容器中可用的尺寸,然后根据父元素的测量规格和子元素中 LayoutParams 参数来决定当前子 View 的 MeasureSpec。

上面的代码,如果用一张表格来表示的话,应该更好理解,请看下表:

parentSpecMode / childLaoutParams EXACTLY(精准模式) AT_MOST(最大模式) UNSPECIFIED(精准模式)
dp/px EXACTLY childSize EXACTLY childSize EXACTLY childSize
match_parent EXACTLY parentSize AT_MOST parentSize UNSPECIFIED 0
Warap_content AT_MOST parentSize AT_MOST parentSize UNSPECIFIED 0

通过此表可以更加清晰的看出,只要提供父容器的 MeasureSpec 和子元素的 LayoutParams, 就可以快速的确定出子元素的 MeasureSpec 了,有了 MeasureSpec 就可以进一步确定出子元素测量后的大小了。

View 工作流程

View 的工作流程主要是指 measure、layout 、draw 这三个流程,即 测量 -> 布局 -> 绘制,其中 measure 确定 View 测量宽高,layout 确定 View 的最终宽高和四个顶点的位置,而 draw 则将 View 绘制到屏幕上。

在讲解 View 的绘制流程之前,我们有必要知道 View 的 measure 何时触发,其实如果对 Activity 生命周期源码有所了解的应该知道,在 onCreate 生命周期中,我们做了 setContentView 把 XML 中的节点转为 View 树的过程,然后在 onResume 可以交互的状态,开始触发绘制工作,可以说 Activity 的 onResume 是开始绘制 View 的入口也不为过,下面看入口代码:

//Act

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值