1、setContentView()
1.1 Activity中,setContentView(),最终是调用到的是PhoneWindow的setContentView()去设置布局。
1.2 PhoneWindow的setContentView()就是去创建一个DecorView加载系统默认的布局R.layout.screen_simple里面有个id为R.id.content(mContentParent),然后解析我们设置的资源布局,然后添加到mContentParent。
1.3 PhoneWindow的setContentView() 只是去创建、解析我们的布局,执行完以后就什么都没干了,那么布局是怎么显示出来的。
2、布局的显示绘制流程
在setContentView(layoutResID)中有一行代码,inflate(resId,mContentParent);会来到 ViewRootImpl 类的 requestLayout() 方法。这就是View绘制流程的入口。
mLayoutInflater.inflate(layoutResID, mContentParent);
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//执行TraversalRunnable的run()方法
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 一系列方法下来之后,进入performTraversals() 这个方法很长..
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
这里只贴一些关键代码- -
private void performTraversals() {
// ... ...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ... ...
performLayout(lp, mWidth, mHeight);
// ... ...
performDraw();
}
接下来进入绘制的关键三部
2.1performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//只贴关键代码....
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
以LinearLayout布局为例
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
// 我们以垂直为例
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// See how tall everyone is. Also remember max width.
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
// 测量子孩子
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
// 高度是子View的高度不断的叠加
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
}
int heightSize = mTotalLength;
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Reconcile our calculated size with the heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
// 设置宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
LinearLayout在调用onMeasure()方法的时候,会不断的循环测量子View,如果是垂直方向,高度是子View的高度叠加,我们现在来看看是怎么测量子View的。
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取当前Parent View的Mode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
int size = Math.max(0, specSize - padding);
//定义返回值存储变量
int resultSize = 0;
int resultMode = 0;
//依据当前Parent的Mode进行switch分支逻辑
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果child的layout_w和h属性在xml或者java中给予具体大于等于0的数值
//设置child的size为真实layout_w和h属性值,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//如果child的layout_wOrh属性在xml或者java中给予MATCH_PARENT
//设置child的size为size,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//如果child的layout_wOrh属性在xml或者java中给予WRAP_CONTENT
//设置child的size为size,mode为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// ......
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
// 如果父 View 是 AT_MOST 就算子 View 是 MATCH_PARENT,
// 其实子View获得的测量模式还是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// ......
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这里再贴下,getChildMeasureSpec()的值介绍:
总结:View的绘制流程第一步是onMeasure(),该方法用来测量和指定布局到底占多大的宽高,因为控件的宽高是由父布局和本身来决定的,所以测量是不断的往内走,而最终确定宽高是由内不断的往外走,是递归的方式。
2.2performLayout()
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
// 调用layout()方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
}
同样以LinearLayout为例
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
// 我们以垂直为例
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
//计算父窗口推荐的子View宽度
final int width = right - left;
//计算父窗口推荐的子View右侧位置
int childRight = width - mPaddingRight;
// Space available for child
//child可使用空间大小
int childSpace = width - paddingLeft - mPaddingRight;
//通过ViewGroup的getChildCount方法获取ViewGroup的子View个数
final int count = getVirtualChildCount();
//获取Gravity属性设置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依据majorGravity计算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
//重点!!!开始遍历
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//获取子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//依据不同的absoluteGravity计算childLeft位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//通过垂直排列计算调运child的layout设置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
总结:从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据第一步performMeasure,来获取子View所的布局大小和布局参数,将子View放在合适的位置上。
2.3performDraw()
private void performDraw() {
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
// ... ...
mView.draw(canvas);
}
3、LayoutInflater分析
3.1如何获取LayoutInflater?
通过context获取系统的服务,context.getSystemService()是一个抽象方法,找到其实现类contextImpl.getSystemService(),通过系统服务注册表获取;
注册表中的集合SYSTEM_SERVICE_FETCHERS,是在静态代码块中就初始化了。
//LayoutInflater 类
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
//ContextImpl实现类的getSystemService()方法,通过系统服务注册表获取
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
//SystemServiceRegistry类
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
// 静态的代码块中
static{
// 注册LayoutInflater服务
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
// 注册很多的其他服务......
}
大致总结下获取LayoutInflater思路,通过Context的实现类ContextImpl获取的,最终是通过SystemServiceRegistry.getSystemService()方法,而SYSTEM_SERVICE_FETCHERS是一个静态的HashMap,初始化是在静态代码块中通过registerService注册了很多服务。
LayoutInflater其实是一个系统的服务,每次获取到的都是同一个静态单例。
3.2如何使用LayoutInflater?
3.3布局的View是如何被实例化的?
我们先看下加载布局的三种方式:
1.View.inflate(context,layoutId,parent);
2.LayoutInflater.from(context).inflate(layoutId,parent);
3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);
View.inflate()方法,最终也是调用到了LayoutInflater.from(context).inflate(layoutId,parent);
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
LayoutInflater.from(context).inflate(layoutId,parent);最终也是调用到了LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot)
所以我们着重看最后的这个方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//获取资源
final Resources res = getContext().getResources();
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//拿到一个xml资源文件解析器
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
.....
//保存root
View result = root;
try {
advanceToRootNode(parser);
//拿到保存在解析器的名字
final String name = parser.getName();
//判断是不是MERGE标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//这里直接加载页面,忽略merge标签,直接传root进rInflate进行加载子view
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//通过标签来获取view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//temp设置布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//把temp当做root传进去rInflateChildren
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
////把temp添加到root中并设置布局参数
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
} finally {
...
}
return result;
}
}
//通过标签创建View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
....
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 判断是不是自定义View,自定义View在布局文件中com.hc.BannerView是个全类名,
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
....
}
}
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 做一些反射的性能优化
try {
// 先从缓存中拿,这是没拿到的情况
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
// 加载 clazz
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 创建View的构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 加入缓存集合集合
sConstructorMap.put(name, constructor);
} else {
}
// 通过反射创建View
final View view = constructor.newInstance(args);
return view;
} catch (NoSuchMethodException e) {
......
}
}
看到这就应该明白了,创建View 对象的时候,当root!=null,最后的参数attachToRoot要传入true