平时在开发安卓的过程当中,View是咱们用的很是很是多的东西.用户所看到的一切关于UI的,都是经过View绘制出来展现到屏幕上的.大多数状况下咱们仅仅了解基本控件的使用方法,咱们是没法作出很是复杂炫酷的自定义View的.咱们须要掌握View的工做原理:测量、布局、绘制流程,掌握了这几个基本的流程咱们才能作出更加完美的自定义View.作起来也更加驾轻就熟.固然,View的工做原理,也是大多数面试所必问的知识点.要想了解工做原理,就只能read the fucking code.java
/***
* ,s555SB@@&
* :9H####@@@@@Xi
* 1@@@@@@@@@@@@@@8
* ,8@@@@@@@@@B@@@@@@8
* :B@@@@X3hi8Bs;B@@@@@Ah,
* ,8i r@@@B: 1S ,M@@@@@@#8;
* 1AB35.i: X@@8 . SGhr ,A@@@@@@@@S
* 1@h31MX8 18Hhh3i .i3r ,A@@@@@@@@@5
* ;@&i,58r5 rGSS: :B@@@@@@@@@@A
* 1#i . 9i hX. .: .5@@@@@@@@@@@1
* sG1, ,G53s. 9#Xi;hS5 3B@@@@@@@B1
* .h8h.,A@@@MXSs, #@H1: 3ssSSX@1
* s ,@@@@@@@@@@@@Xhi, r#@@X1s9M8 .GA981
* ,. rS8H#@@@@@@@@@@#HG51;. .h31i;9@r .8@@@@BS;i;
* .19AXXXAB@@@@@@@@@@@@@@#MHXG893hrX#XGGXM@@@@@@@@@@MS
* s@@MM@@@hsX#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&,
* :GB@#3G@@Brs ,1GM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@B,
* .hM@@@#@@#MX 51 r;iSGAM@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@8
* :3B@@@@@@@@@@@&9@h :Gs .;sSXH@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:
* s&HA#@@@@@@@@@@@@@@M89A;.8S. ,r3@@@@@@@@@@@@@@@@@@@@@@@@@@@r
* ,13B@@@@@@@@@@@@@@@@@@@5 5B3 ;. ;@@@@@@@@@@@@@@@@@@@@@@@@@@@i
* 5#@@#&@@@@@@@@@@@@@@@@@@9 .39: ;@@@@@@@@@@@@@@@@@@@@@@@@@@@;
* 9@@@X:MM@@@@@@@@@@@@@@@#; ;31. H@@@@@@@@@@@@@@@@@@@@@@@@@@:
* SH#@B9.rM@@@@@@@@@@@@@B :. 3@@@@@@@@@@@@@@@@@@@@@@@@@@5
* ,:. 9@@@@@@@@@@@#HB5 .M@@@@@@@@@@@@@@@@@@@@@@@@@B
* ,ssirhSM@&1;i19911i,. s@@@@@@@@@@@@@@@@@@@@@@@@@@S
* ,,,rHAri1h1rh&@#353Sh: 8@@@@@@@@@@@@@@@@@@@@@@@@@#:
* .A3hH@#5S553&@@#h i:i9S #@@@@@@@@@@@@@@@@@@@@@@@@@A.
*
*
* 又看源码,看你妹呀!
*/
复制代码
新建一个自定义View,名字叫MyView,分别在MyView的onMeasure(),onLayout(),onDraw()打上log.面试
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "onMeasure: ---MyView");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.e(TAG, "onLayout: ---MyView");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.e(TAG, "onDraw: ---MyView");
}
复制代码
而后在Activity的布局中添加这个MyView,并在Activity的onCreate(),onStart(),onResume()打上log.canvas
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG, "onCreate: ");
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
}
复制代码
运行demo,看一下log:bash
05-13 22:35:37.130 17346-17346/com.xfhy.demo E/xfhy: onCreate:
05-13 22:35:37.130 17346-17346/com.xfhy.demo E/xfhy: onStart:
05-13 22:35:37.130 17346-17346/com.xfhy.demo E/xfhy: onResume:
05-13 22:35:37.155 17346-17346/com.xfhy.demo E/xfhy: onMeasure: ---MyView
05-13 22:35:37.295 17346-17346/com.xfhy.demo E/xfhy: onLayout: ---MyView
05-13 22:35:37.315 17346-17346/com.xfhy.demo E/xfhy: onMeasure: ---MyView
05-13 22:35:37.315 17346-17346/com.xfhy.demo E/xfhy: onLayout: ---MyView
05-13 22:35:37.315 17346-17346/com.xfhy.demo E/xfhy: onDraw: ---MyView
复制代码
好了,咱们从log中就能够看出,View的绘制实际上是从Activity的onResume()以后才开始的.app
在Activity的启动过程当中,咱们知道,在最后那里,ActivityThread中的handleLaunchActivity,performLaunchActivity,handleResumeActivity,这3个主要的方法完成了Activity的建立到启动工做.ide
ps: 若是有不清楚的在网上查阅一下资料,这里推荐刚哥的书籍:安卓开发艺术探索 第九章oop
下面看一下handleLaunchActivity()方法:源码分析
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
//分析1 : 这里是建立Activity,并调用了Activity的onCreate()和onStart()
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
//分析2 : 这里调用Activity的onResume()
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
}
....
}
复制代码
handleLaunchActivity()主要是为了调用performLaunchActivity()和handleResumeActivity()布局
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
//分析1 : 这里底层是经过反射来建立的Activity实例
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
//底层也是经过反射构建Application,若是已经构建则不会重复构建,毕竟一个进程只能有一个Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
//分析2 : 在这里实例化了PhoneWindow,并将该Activity设置为PhoneWindow的Callback回调,还初始化了WindowManager
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
//分析3 : 间接调用了Activity的performCreate方法,间接调用了Activity的onCreate方法.
mInstrumentation.callActivityOnCreate(activity, r.state);
//分析4: 这里和上面onCreate过程差很少,调用Activity的onStart方法
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
....
}
}
复制代码
主要过程:post
对于分析1中的代码:
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//反射->实例化
return (Activity)cl.loadClass(className).newInstance();
}
复制代码
对于分析2中的代码:
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
......
//实例化PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
.....
//还有一些其余的配置代码
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
....
}
复制代码
对于分析3中的代码:
//首先是来到Instrumentation的callActivityOnCreate方法
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
//而后就来到Activity的performCreate方法
final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
....
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
......
}
复制代码
对于分析4中的代码:
//Activity
final void performStart() {
......
mInstrumentation.callActivityOnStart(this);
......
}
//Instrumentation
public void callActivityOnStart(Activity activity) {
activity.onStart();
}
复制代码
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
.....
//分析1 : 在其内部调用Activity的onResume方法
r = performResumeActivity(token, clearHide, reason);
.....
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
.....
//分析2 : WindowManager添加DecorView
wm.addView(decor, l);
...
}
.....
}
复制代码
细看分析2中的逻辑:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
复制代码
咱们看到,方法里面将逻辑交给了mGlobal,mGlobal是WindowManagerGlobal,WindowManagerGlobal是全局单例.WindowManagerImpl的方法都是由WindowManagerGlobal完成的.咱们跟着来到了WindowManagerGlobal的addView方法.
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
....
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
.....
root.setView(view, wparams, panelParentView);
}
}
复制代码
在这里咱们看到了,实例化ViewRootImpl,而后创建ViewRootImpl与View的联系.跟着进入setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
.....
requestLayout();
.....
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查线程合法性
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
....
performTraversals();
....
}
}
复制代码
一路下来,咱们来到了熟悉的方法面前:performTraversals方法.
performTraversals()方法相信你们都已经很是熟悉啦,它是整个View绘制的核心,从measure到layout,再从layout到draw,所有在这个方法里面完成了,因此这个方法里面的代码很是长,这是确定的.因为本人水平有限,就不每句代码逐行分析了,咱们须要学习的是一个主要的流程.
下面的performTraversals()方法的超精简代码,里面的代码真的超级超级多,下面是主要流程,也是今天的主角
private void performTraversals() {
//分析1 : 这里面会调用performMeasure开始测量流程
measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
//分析2 : 开始布局流程
performLayout(lp, mWidth, mHeight);
//分析3 : 开始绘画流程
performDraw();
}
复制代码
首先,来个主要的流程图,这个是performTraversals()的大体流程.
如上面的代码所示,performTraversals()会依次调用performMeasure、performLayout、performDraw方法,而这三个方法是View的绘制流程的核心所在.
在开始进行理解View的测量流程以前,须要先理解MeasureSpec.
MeasureSpec表明的是32位的int值,它的高2位是SpecMode(也是一个int),低30位是SpecSize(也是一个int),SpecMode是测量模式,SpecSize是测量大小. MeasureSpec至关因而二者的结合. 系统封装了如何从MeasureSpec中提取SpecMode和SpecSize,也封装了用SpecMode和SpecSize组合成MeasureSpec.
public static 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;
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) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
复制代码
SpecMode有3种,分别是
UNSPECIFIED
父容器对View不会有任何限制,要多大给多大,通常是用在系统内部使用,咱们开发的APP用不到.EXACTLY
这种状况对应于match_parent
和具体数值这两种模式,父容器已经检测出View须要的精确大小.AT_MOST
这种状况对应于wrap_content
,父容器指定了一个最大值,View不能超过这个值.通常来讲,View的MeasureSpec由父容器的MeasureSpec和本身的LayoutParams共同决定.由于有了MeasureSpec才能够在onMeasure中肯定测量宽高.
下面是从源码中提取出来的摘要信息,后面会详细看源码分析,这里先提取出来
wrap_content
,那么无论父容器的MeasureSpec是EXACTLY仍是AT_MOST
,最终View的MeasureSpec都是AT_MOST
,这里暂时不用管UNSPECIFIED(咱们用不到).并且View最终的大小不能超过父容器的剩余空间match_parent
,那么要分两种状况
AT_MOST
,那么View也是AT_MOST
.这里不得不引用一张刚哥书籍里面的经典表格来表示一下:
ps:这里必需要强烈推荐一波刚哥的安卓开发艺术探索这本书.没看过这本书,都不敢说本身是学安卓的.去年我做为萌新买了这本书看了一遍,以为受益不浅.今年打算再看一遍,从新梳理一下.好书是值得反复推敲琢磨的.
咱们从ViewRootImpl的performTraversals()开始着手,仔细观察
private void performTraversals() {
//host为根视图,即DecorView
//desiredWindowWidth是Window的宽度
measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
//分析1 : desiredWindowWidth就是Window的宽度,desiredWindowHeight是Window的高度
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//分析2 : 开始测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
measure是从上往下执行的,widthMeasureSpec和heightMeasureSpec一般状况下是由父容器传递给子视图的.可是最外层的根视图,怎么拿到MeasureSpec呢? 在执行performMeasure方法以前,咱们须要拿到最外层的视图的MeasureSpec,看代码
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//若是是MATCH_PARENT,那么就是EXACTLY
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
//若是是WRAP_CONTENT,就是AT_MOST
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//若是是固定的值,也是EXACTLY
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码
最外层的根视图的MeasureSpec只由本身的LayoutParams决定,作本身的主人,舒服.
既然咱们根视图拿到了MeasureSpec,接下来就要拿本身的MeasureSpec教孩子作人了.
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//调用根视图的measure方法,开始测量流程
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
这里的mView就是DecorView.DecorView是一个FrameLayout,而FrameLayout是一个ViewGroup,而ViewGroup是一个View,这个measure方法就是在View里面的. 由于measure是一个final方法,哈哈,因此子类不能覆写它.
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
//调用onMeasure方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
复制代码
measure方法里面有一些检测是否须要从新onMeasure的代码,被我略去了.
onMeasure是View里面的方法,ViewGroup是一个抽象类而且没有重写onMeasure.由于onMeasure方法的实现,每一个都是不同的,好比LinearLayout和FrameLayout的onMeasure方法确定是实现逻辑不同的.
由于DecorView是FrameLayout,因此咱们看看FrameLayout中的onMeasure.
FrameLayout->onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//分析1 : 遍历全部子控件,测量每一个子控件的大小
//参数1:View控件
//参数2:宽MeasureSpec
//参数3:父容器在宽度上已经用了多少了,由于FrameLayout的规则是:前面已经放置的View并不会影响后面放置View的宽高,是直接覆盖到上一个View上的.因此这里传0
//参数4:高MeasureSpec
//参数5:父容器在高度上已经用了多少了
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
......
//分析2 : 测量完全部的子控件的大小以后,才知道本身的大小 这很符合FrameLayout的规则嘛
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
......
}
复制代码
FrameLayout的onMeasure方法中会遍历全部子控件,而后进行全部子控件的大小测量.最后才来设置本身的大小.注意,onMeasure方法的入参MeasureSpec是从父容器传过来的,意思就是给你个参考,你本身看着办吧.
在测量子控件大小的时候会调用ViewGroup的measureChildWithMargins方法,下面是代码:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
//获取子控件的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//分析1: 计算子控件在宽上的MeasureSpec
//参数1:父容器的MeasureSpec
//参数2:这里官方的入参名称是padding,从下面这个传值的形式来看,显然是子控件在宽上不能利用的空间(ViewGroup的左右两边padding+子控件的左右margin+父容器在宽度上已经使用了而且不能再使用的空间)
//参数3:子控件想要的宽度
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);
//分析2: 将measure过程传递给子控件 若是子控件又是一个ViewGroup,那么继续向下传递
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
在measureChildWithMargins方法里咱们首先是看到根据子控件的LayoutParams和父容器的MeasureSpec计算子控件的MeasureSpec,而后将计算出的MeasureSpec经过子控件的measure方法传递下去.若是子控件又是一个ViewGroup,那么它又会重复的measure流程,一直向下传递这个过程,直接最后的那个是View为止.由于View没有子控件,它就不能向下传递了.
因此咱们自定义View(这里指那种直接继承自View)的时候,在onMeasure方法里面,须要根据自身的LayoutParams+父容器的MeasureSpec来计算SpecSize和SpecMode,最后根据业务场景来肯定本身的大小(调用setMeasuredDimension来肯定大小).
注意了,接下来的getChildMeasureSpec方法就比较重要了
//这里来自ViewGroup的getChildMeasureSpec方法,无删减
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//根据父容器的MeasureSpec获取父容器的SpecMode和SpecSize
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//剩下的size
int size = Math.max(0, specSize - padding);
//最终的size和mode
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//父容器有一个肯定的大小
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子控件也是肯定的大小,那么最终的大小就是子控件设置的大小,SpecMode为EXACTLY
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.
//子控件想要本身定义大小,可是不能超过剩余空间 size
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);
}
复制代码
这段代码对应着下面这段总结
wrap_content
,那么无论父容器的MeasureSpec是EXACTLY仍是AT_MOST
,最终View的MeasureSpec都是AT_MOST
,这里暂时不用管UNSPECIFIED(咱们用不到).并且View最终的大小不能超过父容器的剩余空间match_parent
,那么要分两种状况
AT_MOST
,那么View也是AT_MOST
.这段代码对应着上面刚哥总结的那个表格.同时也是measure流程的核心内容.
由于在measureChildWithMargins方法里咱们已经计算出子控件的MeasureSpec,而后经过measure传递给子控件了,若是子控件又是一个ViewGroup,那么它又会重复的measure流程,一直向下传递这个过程,直接最后的那个是View为止.由于View没有子控件,它就不能向下传递了.到这里其实咱们的View的measure流程已经走完了,哈哈,不知不觉.
下面简单画一个流程图,方便理解上面的流程.
从ViewRootImpl的performTraversals方法开始进入View的绘制过程,performTraversals方法里面会有一个performMeasure方法.这个performMeasure方法是专门拿来测量View的大小的.并且会遍历整个View树,所有进行测量.
在performMeasure里面会调用measure方法,而后measure会调用onMeasure方法,而在onMeasure方法中则会对全部的子元素进行measure过程.这至关于完成了一次从父元素到子元素的measure传递过程,若是子元素是一个ViewGroup,那么继续向下传递,直到全部的View都已测量完成.测量完成以后,咱们能够根据getMeasureHeight和getMeasureWidth方法获取该View的高度和宽度.
咱们仍是从ViewRootImpl的performTraversals()开始着手
private void performTraversals() {
....
//开始布局流程
performLayout(lp, mWidth, mHeight);
.....
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
.....
//这里的host实际上是根视图(DecorView)
//参数:left,top,right,bottom 这些位置都是相对于父容器而言的
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
.....
}
复制代码
在performLayout方法里面调用了DecorView的layout方法,而后我发现:layout方法实际上是View这个父类里面的,而后ViewGroup继承了View以后重写了一下,只是调了一下super.layout(l, t, r, b);
,至关于实现仍是在View的layout里面.并且ViewGroup的layout方法是final修饰的,意味着子类不能再重写这个方法了.
//如下是View的layout方法
public void layout(int l, int t, int r, int b) {
......
onLayout(changed, l, t, r, b);
......
}
复制代码
layout方法其实就是调用onLayout方法,若是这里子控件是一个View的话,那么onLayout实际上是空实现.onLayout在ViewGroup是一个抽象方法,若是是一个ViewGroup的话,好比FrameLayout,那么onLayout是须要本身实现的.
//View中的定义
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
//ViewGroup中的定义,没错,这是抽象方法,具体的实现交由实现类去实现
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
复制代码
由于咱们的根视图是DecorView,也就是FrameLayout,那么咱们来看一下FrameLayout的onLayout实现:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//布局子控件 我没看懂这个changed参数是拿来干什么的,好像并无用上(这里已是FrameLayout的onLayout方法的所有代码了)
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
//最左侧
final int parentLeft = getPaddingLeftWithForeground();
//最右侧
final int parentRight = right - left - getPaddingRightWithForeground();
//最顶部
final int parentTop = getPaddingTopWithForeground();
//最底部
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//由于已经measure流程走完了,因此这里是能经过getMeasuredWidth方法获取测量宽度的
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
//实际子控件的left
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
//水平居中
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT: //子控件在父容器的最右侧
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT: //子控件在父容器的最左侧
default:
childLeft = parentLeft + lp.leftMargin;
}
//竖直方向上的gravity
switch (verticalGravity) {
case Gravity.TOP: //位于父容器的顶部
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL: //垂直居中
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM: //位于父容器底部
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//最后给这个子控件一个最终的left,top,right,bottom值
//把这个子控件放在这里
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
复制代码
不一样的ViewGroup的实现类的onLayout方法实现是不同的,是根据自身状况来决定将子控件放在那里的,好比FrameLayout和LinearLayout的onLayout是不同的实现,可是onLayout这个方法最终是将各个子控件有条不紊的放在对应的位置上.
咱们看到在onLayout方法的最后,调用了子控件的layout方法,其实就是将layout流程向下进行传递了.若是子控件仍是ViewGroup的话,那么它又会对它本身全部的子控件进行布局,放置.最后一层一层的往下,直到所有都layout完成.每一个View都知道本身的left,top,right,bottom.这个时候是能够经过View的getWidth和getHeight来获取最终的宽高的.
下面的View的getWidth和getHeight方法的实现,能够看到,就是经过这四个位置来肯定的宽高.
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
复制代码
layout主要是为了肯定该控件以及其子控件的位置和大小.在performLayout中,主要是肯定每一个控件的left+top+right+bottom,performLayout以后它们的位置就已经被肯定了,就只剩下最后一步绘制了.
仍是从ViewRootImpl的performTraversals方法开始分析
private void performTraversals() {
//开始绘画流程
performDraw();
}
private void performDraw() {
......
draw(fullRedrawNeeded);
......
}
private void draw(boolean fullRedrawNeeded){
.....
drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty);
.....
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
......
mView.draw(canvas);
......
}
复制代码
随着方法的调用深刻,发现来到了View的draw方法
public void draw(Canvas canvas) {
.....
/* 注意了这是官方给的注释,谷歌工程师还真是贴心,把draw步骤写的详详细细,给力,点赞 * 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
//1. 绘制背景
if (!dirtyOpaque) {
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
//3. 绘制本身的内容
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
//4. 绘制子控件 若是是View的话这个方法是空实现,若是是ViewGroup则绘制子控件
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)
//6. 绘制装饰和前景
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
//7. 绘制默认焦点高亮显示
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
.....
}
复制代码
注意到,谷歌工程师将draw的步骤完彻底全的写出来了的.还真是贴心啊.draw的基本步骤以下
这里简单提一下dispatchDraw方法,在这个方法里面会去调用drawChild方法,在drawChild里面会调用子控件的draw方法,这至关于完成了draw的传递过程,通知子控件去绘制它本身. 而后若是子控件是ViewGroup,它又会重复上面这个递推.
draw的流程比测量和布局要简单一些,可是须要注意的是,View绘制过程是经过dispatchDraw来传递的.
写一篇深刻(可能只是对我来讲)的文章真的好不容易,期间遇到了不少坑,也学到了不少.以前其实这部分是学过的.可是只有当本身去看源码,一步步分析,输出成文档,才真正理解其中的原理,为何代码要这样写.
可能仍是太菜了吧,写这么一篇水文花了大概4天......