日常开发中,我们经常碰到需要自定义View(包括自定义View和ViewGroup)的地方,并且在面试中也需要我们熟悉或者精通自定义View。毕竟有时候我们需要实现的效果,Android本身是没有提供的,就需要我们去自定义,掌握原理可以让我们在实际使用中更加得心应手。
一、View是如何被添加到屏幕上的
从Activity走起,我们日常是通过Activity的onCreate()方法中的 setContentView(R.layout.activity_main) 给Activity设置布局文件。我们一步一步的往下走,
- 点击setContentView(),到Activity源码
public void setContentView(@LayoutRes int layoutResID) {
// 调用的是Window的setContentView()方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- 查看getWindow()发现返回的是一个Window对象,查看Window
public Window getWindow() {
return mWindow;
}
// 然后到Window对象去查看,看到注释
// he only existing implementation of this abstract class is android.view.PhoneWindow
// 告诉我们它是一个抽象类,有唯一的一个实现类PhoneWindow,所以我们到PhoneWindow中去查看,找到setContentView()方法
- 查看PhoneWindow的setContentView(),这里我们主要查看installDecor()和mLayoutInflater.inflate(layoutResID, mContentParent)。
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 去创建一个DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 根据我们传入的布局资源ID==》layoutResID去解析xml布局资源
// 通过后面源码的分析,我们知道setContentView(layoutId)
// layoutResID是被放在基础容器的FrameLayout中的
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
- 查看PhoneWindow的 installDecor(),省略一下代码,只查看关键代码
private void installDecor() {
...
// 判断DecorView是否为空
if (mDecor == null) {
// 需要去创建DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
...
// 判断mContentParent(是ViewGroup)是否为空
if (mContentParent == null) {
// 根据DecorView去得到了内容容器FrameLayout
mContentParent = generateLayout(mDecor);
}
}
- 查看关键的 PhoneWindow的generateDecor(-1) 方法,这是创建DecorView的方法,通过查看源码我们可以看到DecorView是继承FrameLayout(帧布局),是一个容器。
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
// 重点关注最后一行,在这里直接根据context和featureId创建了一个DecorView
return new DecorView(context, featureId, this, getAttributes());
}
- 查看 PhoneWindow的generateLayout(),根据源码我们知道:根据不同的主题或者一些特性,为窗体PhoneWindow设置了不同的属性。根据features 的不同去得到了一个不同的layoutResource,
protected ViewGroup generateLayout(DecorView decor) {
...
// Inflate the window decor.
// 定义一个Layout的资源
int layoutResource;
int features = getLocalFeatures();
// 根据不同的features和其他判断条件,给layoutResource设置不同的Layout资源
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
...
// 省略一些代码,下面省略的判断条件基本跟上面这个条件大同小异
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// 解析layoutResource布局代表的基础容器,将基础容器装入DecorView
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 根据ID_ANDROID_CONTENT去获取主容器,根据下面从Window中拿过来的注释和代码我们可以看到
// The ID that the main layout in the XML layout file should have.
// public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
... 省略代码
// 将主容器contentParent返回
return contentParent;
}
- 查看 DecorView中的mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);,将layoutResource所代表的基础容器装入DecorView中。
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...省略代码
mDecorCaptionView = createDecorCaptionView(inflater);
// 解析layoutResource
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
// 将layoutResource得到的View装入DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
到此为止的图解,类结构图和视图结构图
类结构图
视图结构图
根据layoutResource的其中一个R.layout.screen_simple,我们知道每个主题设置到的布局是一个线性布局LinearLayout ,下一级就是ViewStub 涉及的ActionBar之类的,最后就是我们刚刚讲到的内容容器FrameLayout(Window中的主容器ID:com.android.internal.R.id.content,看下面代码我们就可以知道就是下面代码中FrameLayout的布局ID)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
注意:视图的绘制从DecorView开始依次递归,ViewGroup —> View的调用:measure、layout、draw
总结:首先系统会创建一个顶层的布局容器DecorView,DecorView是一个容器(ViewGroup)继承自FrameLayout,是PhoneWindow持有的实例,是所有应用程序的顶层View,在系统内部进行初始化。当DecorView创建完成之后,系统会根据应用主题特性去加载一个基础容器,不论基础容器是什么,其中都会有一个id为com.android.internal.R.id.content的FrameLayout容器,而我们通过setContentView(layoutId)设置的xml布局就是被添加到这个FrameLayout容器中。
二、View的绘制流程
- 绘制入口
ActivityThread的handleResumeActivity,我没有具体去查看,ActivityThread中的一些源码跟以前有了很大的变化。
来看看handleMessage这个类就跟网上讲的很多的不同,其实源码怎么变都是大同小异。只是以前怎么调用一些生命周期方法,现在进行了更多的抽取。
- 来看一下我们需要使用的handleResumeActivity涉及到的生命周期方法现在的具体实现
ActivityThread实现ClientTransactionHandler接口,然后实现Activity的各个生命周期方法的具体逻辑。
然后在handleMessage中调用,具体调用TransactionExecutor的execute()方法
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
// TransactionExecutor中去真正的执行Activity的生命周期方法
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
TransactionExecutor的execute()
public void execute(ClientTransaction transaction) {
... 省略代码
// 下面两个方法最终都会走到执行生命周期方法
executeCallbacks(transaction);
executeLifecycleState(transaction);
mPendingActions.clear();
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}
TransactionExecutor的调用生命周期的方法,我们发现调用的ClientTransactionHandler的生命周期方法,这些实现是在ActivityThread中。
private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
ClientTransaction transaction) {
final int size = path.size();
for (int i = 0, state; i < size; i++) {
state = path.get(i);
if (DEBUG_RESOLVER) {
Slog.d(TAG, tId(transaction) + "Transitioning activity: "
+ getShortActivityName(r.token, mTransactionHandler)
+ " to state: " + getStateName(state));
}
switch (state) {
case ON_CREATE:
mTransactionHandler.handleLaunchActivity(r, mPendingActions,
null /* customIntent */);
break;
case ON_START:
mTransactionHandler.handleStartActivity(r, mPendingActions);
break;
case ON_RESUME:
mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
break;
case ON_PAUSE:
mTransactionHandler.handlePauseActivity(r.token, false /* finished */,
false /* userLeaving */, 0 /* configChanges */, mPendingActions,
"LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
mTransactionHandler.handleStopActivity(r.token, false /* show */,
0 /* configChanges */, mPendingActions, false /* finalStateRequest */,
"LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
mTransactionHandler.handleDestroyActivity(r.token, false /* finishing */,
0 /* configChanges */, false /* getNonConfigInstance */,
"performLifecycleSequence. cycling to:" + path.get(size - 1));
break;
case ON_RESTART:
mTransactionHandler.performRestartActivity(r.token, false /* start */);
break;
default:
throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
}
}
}
- 接下来具体查看我们关键的View绘制入口方法handleResumeActivity源码
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
// 回调Activity中onResume()生命周期方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...省略代码
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// wm.addView(decor, l),我们需要去找到这个方法具体的实现的地方
ViewManager wm = a.getWindowManager();
// 初始化窗口属性
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 将DecorView装入窗口
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
// Get rid of anything left hanging around.
cleanUpPendingRemoveWindows(r, false /* force */);
}
- 接下来我们根据getWindowManager在Window中找到具体的实现类WindowManagerImpl,然后在其中去找到wm.add()方法
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
- WindowManagerImpl的wm.addView()
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
- 最终找到具体的实现add方法的地方WindowManagerGlobal的addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...省略代码
// 这里声明了一个ViewRootImpl对象
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
// 将前面声明的root(ViewRootImpl)对象进行实例化
root = new ViewRootImpl(view.getContext(), display);
// 给View设置属性
view.setLayoutParams(wparams);
// 最终将对应的View添加到相应的集合中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 最后root调用setView方法将我们传入的View,LayoutParams属性和panelParentView进行关联
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
- 走到ViewRootImpl的setView(View view, WindowManager.LayoutParams attrs, View panelParentView)方法,然后执行ViewRootImpl的requestLayout()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...省略代码
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
...省略代码
}
+执行ViewRootImpl的 scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 执行这样代码,然后mTraversalRunnable的run方法会被调用
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
... 省略代码
}
}
- 执行mTraversalRunnable的run方法中的ViewRootImpl的doTraversal()
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 将会在这个方法中执行我们View绘制的三大步骤
performTraversals();
...
}
}
- 最后执行ViewRootImpl的 performTraversals() 方法,最终会执行View绘制流程的三个方法,测量、布局、绘制。
// Ask host how big it wants to be
// 找到我们绘制流程第一步测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 然后找到绘制流程的第二步布局
performLayout(lp, mWidth, mHeight);
// 找到我们绘制流程的最后一步,draw
performDraw();
View绘制的详细流程
- 测量performMeasure
- View的measure()方法measure(int widthMeasureSpec, int heightMeasureSpec)
- 在measure方法中调用了**onMeasure(int widthMeasureSpec, int heightMeasureSpec)**方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
- 调用setMeasuredDimension(int measuredWidth, int measuredHeight)
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
- 最终调用了setMeasuredDimensionRaw(int measuredWidth, int measuredHeight),最终得到控件的宽高
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// 最终的结果就是给我们测量的宽高进行保存,实际就是测量我们控件的宽高
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
- 通过回溯,我们发现我们的measure涉及到了MeasureSpec对象
View的测量是通过 尺寸 + 模式 来保存的,将这两个值保存在MeasureSpec。
MeasureSpec是一个32位的int类型的值,前两位保存模式,后30位保存尺寸。
SpecMode(前2位) + SpecSize(后30位)
MeasureSpec中的三种模式
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 不确定,父类没有约束,view可以使任意大小,一般是系统使用
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// 对应的二进制
00000000 00000000 00000000 00000000
// 精确的,父类测量除了View的大小,所以我们view的大小就是specSize
// 对应我们的match_content和固定大小
public static final int EXACTLY = 1 << MODE_SHIFT;
// 对应的二进制
01000000 00000000 00000000 00000000
// 包裹,父容器指定一个可用大小,view最大不能超过这个容器的大小
// 对应我们的wrap_content
public static final int AT_MOST = 2 << MODE_SHIFT;
10000000 00000000 00000000 00000000
// 对应的二进制
我们常用的MeasureSpec的方法:
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
// 将size和mode转换成MeasureSpec
// 这里意思是我们要去size的后30位和mode的前两位
// &:都为1才是1,否则为0,|:只要有一个为1就是1,否则为0
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
MODE_MASK:11000000 00000000 00000000 00000000
~MODE_MASK:00111111 11111111 11111111 11111111
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
- 最后我们回到ViewRootImpl中的performTraversals()中去,去查看DecorView的测量,找到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),通过代码我们可以看到DecorView获取宽高的MeasureSpec调用了getRootMeasureSpec()方法。
DecorView的MeasureSpec是由窗口大小和DecorView自身的LayoutParams决定。
// mWidth窗口容器的宽,lp.width我们的顶级ViewDecorView的宽
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
通过getRootMeasureSpec获取DecorView的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通过前面的查找,我们知道performMeasure方法会走到View的onMeasure,因为DecorView是继承的FrameLayout,所以我们去查看FrameLayout的onMeasure方法。
FrameLayout的OnMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...省略代码
// 遍历所有子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 调用ViewGroup的measureChildWithMargins测量所有孩子的尺寸
// 将父容器的测量规格传递下去
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
}
ViewGroup的measureChildWithMargins
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);
}
获取子View的测量规格
// spec:父容器的测量规格
// padding:父容器已经使用的空间大小
// childDimension:子View的尺寸
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;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
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.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} 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.
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;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
确定View的MeasureSpec
parentMeasureSpec \ childLayoutParams | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
wrap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
总结:
1、ViewGroup测量:measure() --> onMeasure()(需要测量子控件的宽高) --> setMeasuredDimension --> setMeasuredDimensionRaw(保存自己的宽高)
2、View的测量:measure() --> onMeasure() --> setMeasuredDimension() --> setMeasuredDimensionRaw(保存自己的宽高)
注意:我们平时在自定义View的时候,一定要重写OnMeasure()方法,我们从上面View的OnMeasure()方法中可以看到,其中默认调用了getDefaultSize()方法,然后从getDefaultSize的源码中可以看到,不管是AT_MOST还是EXACTLY最终返回的测量结果都是specSize,所以会造成我们在布局中写match_parent和wrap_content没有任何区别
- 布局performLayout
- 1、调用view.layout确定自身的位置,即确定上、下、左、右的值
- 2、如果是ViewGroup,需要在onLayout中确定子view的位置
- 在ViewRootImpl中找到 performLayout() 方法。
// Window的LayoutParams,window的宽、高
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
...省略代码
// mView就是我们的DecorView
final View host = mView;
if (host == null) {
return;
}
...省略代码
// 调用View的layout方法,确定view自身的位置
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...省略代码
}
- View的 layout() 方法
public void layout(int l, int t, int r, int b) {
...省略代码
// layout其实就是为了求解左、上、右、下的值
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 通过setFrame去获取mLeft、mTop、mBottom、mRight的值
// 确定View自身的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 调用了onLayout方法,是一个空方法,如果是ViewGroup,就需要实现这个方法,然后确定子View的位置
onLayout(changed, l, t, r, b);
...省略代码
}
...省略代码
}
- View的 setFrame() 方法
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
// 获取左、上、右、下的值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
总结:布局的流程
1、ViewGroup --> layout(确定自己的位置,四个点left,top,right,bottom的位置) --> onLayout(确定子view的位置)
2、View --> layout(确定自己的位置,四个点left,top,right,bottom的位置)
- 绘制performDraw
- 绘制背景drawBackground(canvas)
- 绘制自己onDraw(canvas)
- 绘制子View,dispatchDraw(canvas)
- 绘制前景,滚动条等onDrawForeground(canvas)
在ViewRootImpl中找到 performDraw() 方法,然后在其中找到 draw() 方法。
...省略代码
try {
// 找到draw方法
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...省略代码
ViewRootImpl的 draw(),然后在方法中找到drawSoftware()
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
最终在drawSoftware()方法中找到mView.draw(canvas),然后走到View的draw()方法中去
View的draw()
public void draw(Canvas canvas) {
// 关键代码,绘制流程
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
}
我们查看ViewGroup对View的绘制,在ViewGroup中搜索dispatchDraw(canvas),ViewGroup为我们实现了这个方法。
// 截取一部分代码,还有其他很多源码,有兴趣可以自己观摩
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 遍历所有的子View,然后调用drawChild进行子View的绘制
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
// 遍历所有的子View,然后调用drawChild进行子View的绘制
more |= drawChild(canvas, child, drawingTime);
}
}
ViewGroup的drawChild()
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 最终又调用了View的draw方法
return child.draw(canvas, this, drawingTime);
}