1霸琴、setContentView()
1.1 Activity中雄家,setContentView(),最終是調(diào)用到的是PhoneWindow的setContentView()去設(shè)置布局氮双。
1.2 PhoneWindow的setContentView()就是去創(chuàng)建一個DecorView加載系統(tǒng)默認的布局R.layout.screen_simple里面有個id為R.id.content(mContentParent)碰酝,然后解析我們設(shè)置的資源布局,然后添加到mContentParent戴差。
1.3 PhoneWindow的setContentView() 只是去創(chuàng)建送爸、解析我們的布局,執(zhí)行完以后就什么都沒干了暖释,那么布局是怎么顯示出來的袭厂。
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();
//執(zhí)行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;
}
}
}
這里只貼一些關(guān)鍵代碼- -
private void performTraversals() {
// ... ...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ... ...
performLayout(lp, mWidth, mHeight);
// ... ...
performDraw();
}
接下來進入繪制的關(guān)鍵三部
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) {
//只貼關(guān)鍵代碼....
// 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);
// 設(shè)置寬高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
}
LinearLayout在調(diào)用onMeasure()方法的時候橄杨,會不斷的循環(huán)測量子View,如果是垂直方向照卦,高度是子View的高度疊加式矫,我們現(xiàn)在來看看是怎么測量子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;
//依據(jù)當前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的數(shù)值
//設(shè)置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
//設(shè)置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
//設(shè)置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()的值介紹:
總結(jié):View的繪制流程第一步是onMeasure()惯悠,該方法用來測量和指定布局到底占多大的寬高,因為控件的寬高是由父布局和本身來決定的竣况,所以測量是不斷的往內(nèi)走克婶,而最終確定寬高是由內(nèi)不斷的往外走,是遞歸的方式丹泉。
2.2performLayout()
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
// 調(diào)用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右側(cè)位置
int childRight = width - mPaddingRight;
// Space available for child
//child可使用空間大小
int childSpace = width - paddingLeft - mPaddingRight;
//通過ViewGroup的getChildCount方法獲取ViewGroup的子View個數(shù)
final int count = getVirtualChildCount();
//獲取Gravity屬性設(shè)置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依據(jù)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;
}
//重點G橛!摹恨!開始遍歷
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);
//依據(jù)不同的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;
//通過垂直排列計算調(diào)運child的layout設(shè)置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
總結(jié):從上面分析可以看出layout也是從頂層父View向子View的遞歸調(diào)用view.layout方法的過程,即父View根據(jù)第一步performMeasure晒哄,來獲取子View所的布局大小和布局參數(shù)睁宰,將子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獲取系統(tǒng)的服務(wù),context.getSystemService()是一個抽象方法孝赫,找到其實現(xiàn)類contextImpl.getSystemService(),通過系統(tǒng)服務(wù)注冊表獲群旆青柄;
注冊表中的集合SYSTEM_SERVICE_FETCHERS,是在靜態(tài)代碼塊中就初始化了违孝。
//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實現(xiàn)類的getSystemService()方法刹前,通過系統(tǒng)服務(wù)注冊表獲取
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;
}
// 靜態(tài)的代碼塊中
static{
// 注冊LayoutInflater服務(wù)
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
// 注冊很多的其他服務(wù)......
}
大致總結(jié)下獲取LayoutInflater思路,通過Context的實現(xiàn)類ContextImpl獲取的雌桑,最終是通過SystemServiceRegistry.getSystemService()方法喇喉,而SYSTEM_SERVICE_FETCHERS是一個靜態(tài)的HashMap,初始化是在靜態(tài)代碼塊中通過registerService注冊了很多服務(wù)校坑。
LayoutInflater其實是一個系統(tǒng)的服務(wù)拣技,每次獲取到的都是同一個靜態(tài)單例。
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()方法,最終也是調(diào)用到了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);最終也是調(diào)用到了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("<merge /> 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設(shè)置布局參數(shù)
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//把temp當做root傳進去rInflateChildren
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
////把temp添加到root中并設(shè)置布局參數(shù)
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
} finally {
...
}
return result;
}
}
//通過標簽創(chuàng)建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 {
// 做一些反射的性能優(yōu)化
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);
// 創(chuàng)建View的構(gòu)造函數(shù)
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 加入緩存集合集合
sConstructorMap.put(name, constructor);
} else {
}
// 通過反射創(chuàng)建View
final View view = constructor.newInstance(args);
return view;
} catch (NoSuchMethodException e) {
......
}
}
看到這就應(yīng)該明白了沮榜,創(chuàng)建View 對象的時候,當root喻粹!=null蟆融,最后的參數(shù)attachToRoot要傳入true