Android-⾯试官:简述⼀下-View-的绘制流程,这个都答不出来还想进⼤⼚
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
…
} else {
//将contentView加载到DecorVoew当中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
…
}
private void installDecor() {
…
if (mDecor == null) {
//实例化DecorView
mDecor = generateDecor(-1);
…
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
/
/获取Content
mContentParent = generateLayout(mDecor);
}
…
}
protected DecorView generateDecor(int featureId) {
…
return new DecorView(context, featureId, this, getAttributes());
}
通过generateDecor()new⼀个DecorView,然后调⽤generateLayout()获取DecorView中content,最终通过inflate将Activity视图添加
到DecorView中的content中,但此时DecorView还未被添加到Window中。添加操作需要借助ViewRootImpl。
ViewRootImpl的作⽤是⽤来衔接WindowManager和DecorView,在Activity被创建后会通过WindowManager将DecorView添加
到PhoneWindow中并且创建ViewRootImpl实例,随后将DecorView与ViewRootImpl进⾏关联,最终通过执
⾏ViewRootImpl的performTraversals()开启整个View树的绘制。
关于Activity在何时将DecorView添加到Window以及何时创建 ViewRootImpl,这块内容牵扯⾯⽐较⼴,涉及到Activity启动流程、ActivityManagerService(AMS)、WindowManagerService(WMS),内容太过于深⼊加上作者能⼒有限就不误⼈⼦弟了。如有兴趣推荐查阅刘皇叔《Android进阶解密》,书中对这⽅⾯内容讲解还是⽐较全⾯的 。
2. 绘制过程
从第⼀⼩节可知,View的绘制是从ViewRootImpl的performTraversals()⽅法开始,从最顶层的View(ViewGroup)开始逐层对每个View进⾏绘制操作,下⾯来看⼀下该⽅法部分源代码:
private void performTraversals() {
…
//measur过程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
…
//layout过程
performLayout(lp, desiredWindowW
idth, desiredWindowHeight);
…
//draw过程
performDraw();
}
这⽅法⼤概有⼏百⾏,机智的作者抽出三句精华呈现给⼤家~~~
measure:为测量宽⾼过程,如果是ViewGroup还要在onMeasure中对所有⼦View进⾏measure操作。
layout:⽤于摆放View在ViewGroup中的位置,如果是ViewGroup要在onLayout⽅法中对所有⼦View进⾏layout操作。
draw:往View上绘制图像。
⽰意图如下: 确实不想画图了,从刚哥的书⾥拍⼀张吧~~~
制作android软件流程
2.1 Measure
performMeasure()源码
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
} finally {
}
}
可以看出从mView(最顶层ViewGroup)开始进⾏测量操作,然后逐层遍历View并执⾏measure操作。
MeasureSpac
Measure是View绘制三个过程中的第⼀步,提到Measure就不得不提MeasureSpac它是⼀个32位int类型数值,⾼两位SpacMode代表测量模式,低30位SpacSize代表测量尺⼨,是View的内部类,源码如下:
public class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
}
内部也包含三种测量模式:
**UNSPECIFIED :**⽗布局不会对⼦View做任何限制,例如我们常⽤的ScrollView就是这种测量模式。
**EXACTLY :**精确数值,⽐如使⽤了match_parent或者xxxdp,表⽰⽗布局已经决定了⼦View的⼤⼩,通常在这种情况下View的尺⼨就是SpacSize
**AT_MOST :**⾃适应,对应wrap_content⼦View可以根据内容设置⾃⼰的⼤⼩,但前提是不能超出⽗ViewGroup的宽⾼。
注意点:
在我们⾃定义View的过程中都会在onMeasure中进⾏宽⾼的测量,这个⽅法会从⽗布局中接收两个参
数widthMeasureSpac和heightMeasureSpac,所以⼦布局的宽⾼⼤⼩需要受限于⽗布局。
在⾃定义View宽⾼测量的过程中,我们需要获取MeasurSpac中的宽⾼和测量模式,⾃定义ViewGroup也必须给⼦View传
递MeasurSpac,Android也给我们提供了计算MeasurSpac 和通过MeasurSpac 获取相应值的⽅式,都位于MeasurSpac中,具体代码如下:
public static class MeasureSpec {
public static int makeMeasureSpec( int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK)
}
}
从ViewGroup到View对尺⼨和模式进⾏了⼀次封装和拆解,其⽬的是为了减少对象的创建,避免造成不必要的内存浪费。LayoutParams
在刚接触Android的时候经常有⼀个疑问,为什么View设置⾃⼰的宽⾼,还要创建⼀个xxx.LayoutParams?前⾯也提到了,⼦View的宽⾼是要受限于⽗布局的,所以不能通过setWidth或者setHeight直接设置宽⾼的,另外 LayoutParams的作⽤不仅如此,⽐如⼀个View的⽗布局是RelativeLayout,可以通过设置RelativeLayout.LayoutParams的above,below等属性来调整在⽗布局中的位置。
⾃定义View宽⾼测量演⽰
创建⼀个类继承View,重写其onMeasure()⽅法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//默认宽
int defaultWidth = 0;
//默认⾼
int defaultHeight = 0;
setMeasuredDimension(
getDefaultSize(defaultWidth, widthMeasureSpec),
getDefaultSize(defaultHeight, heightMeasureSpec));
}
⼀般的⾃定义View中,如果对宽⾼没有特殊需求可直接通过getDefaultSize()⽅法获取,该⽅法位于View中源码如下:
public static int getDefaultSize(int size, int measureSpec) {
//默认尺⼨
int result = size;
//获取测量模式
int specMode = Mode(measureSpec);
//获取尺⼨
int specSize = Size(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
从代码分析可知,获取mode和size后会分别对三种测量模式进⾏判断,UNSPECIFIED使⽤默认尺⼨,⽽AT_MOST和EXACTLY使⽤⽗布局给出的测量尺⼨。尺⼨计算完毕后通过setMeasuredDimension(width,height)设置最终宽⾼。
2.2 Layout
performLayout()部分源码:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
…
final View host = mView;
if (host == null) {
return;
}
host.layout(0, 0, MeasuredWidth(), MeasuredHeight());
…
}
跟measure类似,同样是从mView(最顶层ViewGroup)开始进⾏layout操作,随后逐层遍历。layout(l,t,r,b)四个参数分别对应左上右下的位置,从⽽确定View在ViewGroup中的位置。下⾯来看⼀下layout()部分源码:
public void layout(int l, int t, int r, int b) {
…
//通过setOpticalFrame()和setFrame()⽼确定四个点的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
…
//调⽤onLayout(),ViewGroup须重写此⽅法
onLayout(changed, l, t, r, b);
OpticalFrame()和setFrame()⽼确定四个点的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
…
//调⽤onLayout(),ViewGroup须重写此⽅法
onLayout(changed, l, t, r, b);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论