主目录见:Android高级进阶知识(这是总目录索引)
今天文章内容主要是承接《setContentView()的源码分析》的,上篇主要是讨论了view的加载,今天主要讨论view的绘制工作。以下内容会稍微导致身体不适,如果脑子还没放空,那么拖出去洗脑一小时。。。
一.目标
这篇的目标非常明确了,就是死磕到底,没办法,这块骨头是必须要啃的,权当磨牙:
1.了解测量,布局,绘制三个过程;
2.同时了解一些小坑:为什么在onCreate可以在线程中更新UI?等。。
3.了解一种在onCreate时候延迟加载的策略:
getWindow().getDecorView().post(new Runnable() {
@Override public void run() {
myHandler.post(mLoadingRunnable);
} });
当然第三种不会作为本篇重点,如果再讲这个本篇就说不完了,但是会稍微提一下,表示敬意,如果你是处女座可以留言,我可以再开一篇来讲。。。
二.源码分析
1.ActivityThread handleResumeActivity
分析源码的入口是非常关键的,不然就是一步错步步错,为什么要从这个地方开始分析呢?我们前面说过,我们setContentView完之后加载进来的布局都是在DecorView下面的,DecorView又是在PhoneWindow下面的,那么DecorView及DecorView里面的视图的绘制过程是怎样的呢?这个答案比较长,得需要从Activtiy的启动开始讲,那么我们这里直接给出答案:就是在Activity的onResume()之前会调用ActivityThread的handleResumeActivity方法,这里面会有一系列的操作。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
//防止身体不适,删除自认为跟讲解无关代码
.......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
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;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, 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 */);
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
+ r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
+ isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
//注意到这句!!这句!!这句!!
r.activity.makeVisible();
}
}
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
惨无人道呀,刚上来就发着一大段代码,吓唬人呀,其实我也很委屈,源码就是这么长,如果不想看就跟着我的文字走,效果一样,我们看到一个方法r.activity.makeVisible();从判断条件 if (r.activity.mVisibleFromClient) 我们也知道,这个地方的Acitivity快显示了,那显示之前得绘制布局好呀,所以我们看这个方法:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
这个方法比较简单,第一句判断mWindowAdded是否被添加,显然还没添加,因为下面添加完才改了这个标志为true,所以我们看到第一句getWindowManager()方法会返回一个ViewManager对象,其实ViewManager是个接口,所以我们要去看getWindowManager()到底返回的是什么。
public WindowManager getWindowManager() {
return mWindowManager;
}
其实就是返回mWindowManager对象,那么这个对象又是什么时候被赋值呢?这个偷偷告诉你,在handleResumeActivity()方法调用之前就已经赋值了,是在Activity的attach()方法设置的:
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
可以很容易看出来就是调用window的setWindowManager(),所以我们找phoneWindow,但是没有找到,那么就从父类Window查找,我们找到了这个方法:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
非常明确,看看这句mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);其实就是这句赋值的,我们看到vm强制转换成WindowManagerImpl,其实WindowManagerImpl就是继承的WindowManager,我们这里直接看WindowManagerImpl的createLocalWindowManager()方法:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
我们这里找到wm对象是WindowManagerImpl类的实例了,所以我们回到makeVisible()方法,继续看下一句wm.addView(mDecor, getWindow().getAttributes()),我们看看WindowManagerImpl的addView这一句做了些什么鬼:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
心很累呀,到处跳来跳去。。。。坚强地继续看到 mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow)这句,这个mGlobal又是什么鬼?这里的mGloabal是WindowManagerGlobal对象,主要是window的处理类,所以我们看WindowManagerGlobal的addView()方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//省略一些判空操作及无关讲解内容
....
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
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, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
我们看到我们的主角ViewRootImpl被实例化出来了,大家欢迎!!欢迎!!欢迎!!,实例化出来然后被加到mRoots(这是一个存放ViewRootImpl实例的Arraylist)中,同时decorView被添加进mViews中,最后我们看到调用了root.setView()方法,所以我们看ViewRootImpl的setView里面做了啥?
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
}
.......
requestLayout();
..........
}
}
我们看到这个方法主要是讲decorView设置给了ViewRootImpl,同时调用了requestLayout()方法,这个方法大家用的不少了。我们看requestLayout()方法的具体实现是啥:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
我们看到第一个方法checkThread(),这个方法正好说明了为什么在onCreate()方法里面可以在子线程中更新UI了,因为在onCreate时候还没有检查线程呀,我们看下这个方法就会豁然开朗:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
那个侥幸在子线程更新ui的货,你站出来,保证不打你。。。。你正好玩了个擦边球,算你狠。看完这个我们接下来看scheduleTraversals()做了啥?
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里的 mChoreographer.postCallback()的调用里面有个参数传进去mTraversalRunnable,其实地方会回调mTraversalRunnable(这个是Runnable对象)里面的run方法,我们来看这个run执行了啥?
@Override
public void run() {
doTraversal();
}
一句话,很喜欢这么清爽的方法,我们直接跟踪进去:
void doTraversal() {
...........
performTraversals();
..........
}
这个地方勇敢地删除所有的方法,就是为了凸显这个方法!!!这个方法是在ViewRootImpl里面的,我们以后的测量,布局,绘制都在这个方法里面,也就是这几个方法的入口。Are you kidding?玩了这么久才找到入口???
2.入口ViewRootImpl performTraversals
我们终于找到真正的入口了,兴奋不已,赶紧往下看:
private void performTraversals() {
.......
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
........
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
//1.绘制
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
.........
//2.布局
performLayout(lp, mWidth, mHeight);
.........
//3.绘制
performDraw();
}
这个方法其实巨长,如果一个字一个字往下看,那今天这篇就不用写了,所以为了正本溯源,我们就留下我们要一一讲解的步骤。首先我们看下第一个留下来的方法getRunQueue().executeAction(mAttachInfo.mHandler),这个方法为什么留下来呢,其实原因就是前面说到的:
getWindow().getDecorView().post(new Runnable() {
@Override public void run() {
myHandler.post(mLoadingRunnable);
} });
这个地方post的action,到这里才会执行。所以我们知道等我们绘制完毕后才会执行mLoadingRunnable,这就是为什么要两次post的原因。为了讲绘制之前把障碍都扫清楚,我们再来看下getRootMeasureSpec()方法:
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;
}
这个方法很简单,其实就是设置宽高为屏幕的大小,这也就是为什么根视图decorView为啥是屏幕宽高的原因。好啦现在我们可以开始讲我们的performMeasure方法了。
3.performMeasure
在讲之前先贴图镇贴,此图如果见过请忽略,也不是我画的。。。我们知道绘制其实就是调用的performMeasure这个方法,那我们很理所应当就是跟进来:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
我们看到这里调用了 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)这个方法,那么这里的mView是什么东西呢,当然是从根视图开始measure嘛,那就是decorView了,我们知道decorView是个FrameLayout本质上(当然如果你子视图有Linearlayout或者其他就又会调用不同的Measure方法了),所以我们就看FrameLayout的measure方法,可是我们在FrameLayout并没有找到measure方法呀,其实我们是在View里面的measure方法里面才又调用了FrameLayout里面的onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
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);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
.........
}
我们看到程序里面遍历了父视图下面的子视图,然后调用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);
}
我们看到这个其实是根据父视图的大小还有mode,以及自己的Padding和Margin还有LayoutParams来测量,我们先看下这里面的方法getChildMeasureSpec()干了什么:
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;
//默认Root View的Mode就是EXACTLY
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果子视图给的宽高是>0且是确切值则直接设置
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果child的layout_width属性在xml或者java中给予MATCH_PARENT
//设置child的size为size,mode为EXACTLY
// 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.
//如果child的layout_width属性在xml或者java中给予WRAP_CONTENT
//设置child的size为size,mode为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//省略其他的mode和size的代码
.......
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
可以看到根据mode和size测量出自己的大小之后,会把mode和size调用makeMeasureSpec继续合成。
然后我们继续看measureChildWithMargins方法最后会在调用View的measure,如果这个View还是个ViewGoup的话,那就会递归调用measure方法。如果最后是个View呢?那就会调用View的measure呀,也就是会调用到onMeasure(),这个也是我们自定义View会重写的方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
我们看到这个方法里面代码就一句setMeasuredDimension()方法的调用。我们直接看进去:
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方法,我们就跟进去:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
简单呀,就是将这个宽高保存,所以我们只有测量过后才能获取到这个测量的宽高。也就是自定义控件onMeasure之后一定要调用setMeasuredDimension方法的原因,你要保存一下呀。到这里我们的测量工作就完成啦。
测量总结:由于测量整个过程比较复杂,我这里做个总结
1.MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:
MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;
2.View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
3.最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
4.ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算
5.View的布局大小由父View和子View共同决定
6.使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值.
4.performLayout
我们看完测量的过程了,我们就看看怎么把这些控件布局到界面了,布局也是一个递归调用的过程,我们来看下这个方法。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
.......
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
......
}
我们看到这个地方调用mView的layout方法,同样第一次进来还是调用的根视图的layout,所以我们看到DecorVIew(FrameLayout)的layout方法,这个方法在Framelayout的父类ViewGroup中才有,我们找到它了:
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
我们看到这个地方调用了super.layout方法,即调用了View的layout方法,所以我们直接进入View的这个方法:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
...........
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
我们看到这个layout方法显示保存了上下左右这些的数值,又调用了onLayout方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
额。。。。View的这个方法是空实现,其实是合理的如果是View的话,那就自己实现,如果是ViewGroup那就调用他的子类去实现他,因为我们DecorView是FrameLayout,所以我们再去看FrameLayout的onLayout:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
跟我们预想一样,让子类自己去布局自己,所以这也是一个递归的过程,如果是View则自己实现布局,如果是ViewGoup则递归调用layout:
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();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
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;
}
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;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
我们看到这个方法也是遍历子视图,然后分别去调用子视图的layout方法。
到这里我们的布局也讲解完毕了,我们这里也做个总结吧。
总结:
1.View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。
2.measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。
3.凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的,这个在LayoutInflater.inflater方法里面可以找到答案。
5.performDraw
好啦,我们终于到达最后一步绘制啦,到这里身体是不是开始不适。。。其实我也开始不适,只能用图来缓解缓解,盗图可耻,但是可爱呀!!!
看完图之后我们继续看我们的performDraw方法:
private void performDraw() {
......
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
}
我们看到这里又调用了ViewRootImpl的draw方法,所以我们直接看到draw方法:
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
...
}
这里我们调用drawSoftware方法:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
...
mView.draw(canvas);
...
return true;
}
几经波折,我们终于找到熟悉的代码了mView.draw(canvas),我们知道ViewGroup是没有重写draw方法的,所以我们直接看View的draw方法。
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*这个是官方的注释,大概就是说了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
int saveCount;
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
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(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);
// we're done...
return;
}
//底下省略verticalEdges ,horizontalEdges不为false的绘制流程
.......
}
我们看到,这个地方如果verticalEdges ,horizontalEdges都为false的时候我们会跳进进行下一步的绘制,我们这个地方就分析这种情况的绘制流程,其实另外下面的绘制流程是差不多的,顶多多了边缘的绘制。
5.1).现在我们这里讲第一步:drawBackground
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
//根据layout过程确定的View位置来设置背景的绘制区域
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
我们看到前面先把mBackground设置给了backgroud局部变量,接着设置了背景的bound范围,然后最后调用background.draw(canvas)来将背景绘制进画布。
5.2).现在我们这里讲第三步:对View的内容进行绘制
onDraw这个方法ViewGroup里面也是没有的,所以我们还是来看View
protected void onDraw(Canvas canvas) {
}
可以看到这是一个空的方法,这是交给我们的自定义view自己来实现的,因为系统不知道我们的View是长啥样的,所以要我们自己决定。
5.3).现在我们这里讲第四步:对所有子View的进行绘制
这个方法因为是对所有子View的绘制,所以View里面这个方法是空的,ViewGroup里面才有。
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
......
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
我们看到这边调用了drawChild方法,我们直接进去看下:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
我们看到其实就是调用View的draw方法,也就是上面说的,交给View自己去决定要画什么。
5.4).现在我们这里讲第六步:绘制一些滚动条,前景图片等
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
我们看到这个方面前面就绘制了ScrollIndicators,和ScrollBars,然后获取前景图片,如果不为空则最后调用Drawbale的draw方法绘制进canvas( foreground.draw(canvas))。到这里我们的draw流程都说清楚了,总结就是那张图了,说明的非常清楚,所以就不总结了。
不是说不总结了怎么马上又总结了:这是整片文章的总结,整篇说了View的绘制流程,过程还是比较复杂的,当然有些问题可能不能全部覆盖,所以希望如果有什么知识点可以留言提出来,我会加一篇文章进行解答,如果没有那就算了,你可以上天了。。。