以下代码基于 ANDROID SDK 28也就是android9的。
View的绘制是个老生常谈的问题了,作为一名android开发人员这是基础必备的知识了,了解了view的绘制流程也能更加熟练地掌握自定义控件的技能。下面是本人用一点浅薄的知识分析的,如有不对,敬请指出。
view的首次绘制
==view的第一次绘制是在activityThread中的handleResumeActivity中去实现的==,activityThread是操作activity的一个线程,非常重要的一个类。这里先简单地说下吧。
- 首先整个系统开机(加载boot_loader)的时候
- 会先出现一个init进程
- init进程fork一个zygote进程
- zygote进程fork出system_server
- system_server启动后,很多系统的进程例如AMS,WMS,PMS都是该进程创建后启动的
- launcher进程启动后,触发了startActivity,通过binder机制告诉system_server
- system_server接收到要启动activity的消息,system_server中的AMS会去通过socket告诉zygote进程,fork出app的进程。
- app进程执行activityThread的main入口,并初始化==ApplicationThread==(继承了activityThread,同时实现了IBinder的接口)用于和AMS交互
- applicationThread通过binder机制告诉system_server,我要绑定AMS,system_server收到通知,向app进程发送handleBindApplication请求,并scheduleLaunchActivity请求。
- app进程收到请求后,通过handler向activityThread发送对应的message,执行对应的消息。最后完成对应的生命周期方法onCreate/onResume等。
看完了以上的一个简单介绍,是不是了解一点activityThread,还不了解没关系,记住就好了...上来就跑题真的好吗...那我们继续看下activityThread的handleResumeActivity里做了啥吧。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
if (r.window == null && !a.mFinished && willBeVisible) {
//获取一个window对象
r.window = r.activity.getWindow();
//获取decorView,顶级view
View decor = r.window.getDecorView();
//先设置为不可见
decor.setVisibility(View.INVISIBLE);
//拿到viewManger
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,操作decorView的大佬!
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//添加了,它添加了!就是这里,可以看到,是我们的viewManager做的!添加了decorView
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
}
}
可以看到在handleResumeActivity中通过wm去addview来添加decorview,wm是windowmanagerImpl。
windowmangerImpl.addView->windowManagerGlobal.addView->ViewRootImpl.setView->ViewRootImpl.requestLayout就触发了第一次的view的绘制。
这就是view的第一次绘制,是不是很简单...下面我们还是从第一次说起...众所周知,activity必须在onCreate里setContentview才能看到界面,这是为啥呢,why ,how?继续看
setContentView源码分析
activity启动之后会先在oncreate里去setContentView,如下是activity中的源码:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow得到的是window,window是个抽象类,唯一实现的是PhoneWindow。去phoneWindow中查看是如何setContentView的;
public void setContentView(int layoutResID) {
if (this.mContentParent == null) {
//判断decorview是否为null,如果为null,则创建一个;
this.installDecor();
} else if (!this.hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//已经存在了,则移除所有的view, FEATURE_CONTENT_TRANSITIONS:过渡动画的属性;
this.mContentParent.removeAllViews();
}
...
}
重点看这行mLayoutInflater.inflate(layoutResID, this.mContentParent);,有木有很熟悉的赶脚,对,是加载xml的常用方法,多说一句,通过阅读源码可以得出结论:
- 1.root为null,attachRoot不管为什么,都没意义;
- 2.root不为null,attachRoot为true,会给布局添加一个父布局,即root;
- 3.root不为null,attachRoot为false,会把布局最外层layout的属性进行设置,当被添加到父view中,这些属性会生效;
- 4.root不为null,没有设置attachRoot属性,则默认为true。
layoutinflater是如何加载的?这里简单概述一下。
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
//这是一个同步的方法
synchronized(this.mConstructorArgs) {
Context inflaterContext = this.mContext;
AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)this.mConstructorArgs[0];
this.mConstructorArgs[0] = inflaterContext;
Object result = root;
try {
InflateException ie;
try {
int type;
while((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
//如果是开始和结束的标签不做处理
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription() + ": No start tag found!");
}
String name = parser.getName();
//先对merge标签进行处理
if ("merge".equals(name)) {
if (root == null || !attachToRoot) {
//root为空或者不附到根部局则报错,所以merge标签只能在有一个有效的viewgroup而且attachroot为true才能使
throw new InflateException("<merge /> can be used only with a valid ViewGroup root and attachToRoot=true");
}
//可以看到标签为merge的xml,parent为root
this.rInflate(parser, root, inflaterContext, attrs, false);
} else {
//通过tag创建view
View temp = this.createViewFromTag(root, name, inflaterContext, attrs);
LayoutParams params = null;
if (root != null) {
//先生成params
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
this.rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
//添加view
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException var19) {
ie = new InflateException(var19.getMessage(), var19);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception var20) {
ie = new InflateException(parser.getPositionDescription() + ": " + var20.getMessage(), var20);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
} finally {
this.mConstructorArgs[0] = lastContext;
this.mConstructorArgs[1] = null;
Trace.traceEnd(8L);
}
return (View)result;
}
}
来,继续看这一行 View temp = this.createViewFromTag(root, name, inflaterContext, attrs);,那么是如何通过tag创建view的呢?
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//是系统的view
view = onCreateView(parent, name, attrs);
} else {
//是自定义的view,直接创建
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
...
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
//可以看到最终都调用了createView,区别就是如果是系统的view则直接
return createView(name, "android.view.", attrs);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//加载对应的类
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//反射获取构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//做个缓存,下次直接使用,提高效率
sConstructorMap.put(name, constructor);
} else {
...
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//使用构造函数创建view
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
//处理viewstub
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
//省去了一大推抛出异常的catch块代码
} catch (Exception e) {
} finally {
}
}
删掉了一部分代码,注意看三个Factory,分别调用了onCreateView方法,然后创建了view。ok,比较明了了,现在的问题点就是这三个factory是啥,是什么时候创建的?
这三个factory在activity.attach里面已经被设置了。
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
}
setPrivateFactory是个隐形方法,外部不能调用。而另外两个外部用户可以调用。
且factory2是调用的fragment的createView方法;
所以上面的factory和factory2 是系统hook给我们的方法,如果都没有设置,则走默认的创建view的流程。
默认的view的创建流程,最终还是通过反射,去获取类的构造函数,然后去新建一个view。
以上就是setContentView加载view的流程,至此我们简单总结一下从activity创建到view的创建的流程:
- 1.应用启动之后,执行activity的oncreate方法,在方法里setContentView;
- 2.通过调用phonewindow的setContentView,执行layoutinfalter的inflate方法去加载布局;
- 3.在inflate方法里,用XmlPullParser解析xml;
- 4.解析过程中,可以手动设置factory,如果未设置走默认创建view的流程,也就是通过反射调用类的构造新建view。
view的绘制流程
从首次view的绘制可知,viewRootImpl调用requestLayout触发了view的第一次绘制。
那么我们继续从这里看。
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查是否在主线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ok,这个方法里首先检查线程,然后把变量置位true,最后执行scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
ok。终于到了performTraversals了.这里代码特别长,总的来说是这个流程:
performTraversals -> performMeasure -> performLayout -> performDraw
performMeasure都会先去判断是否需要重新测量?需要的话调用measure,其他两个方法同理。
View的测量-onMeasure
自上而下进行遍历。measure方法主要接收两个参数widthMeasureSpec和heightMeasureSpec。而这两个值是通过父视图经过计算后传给子视图的,而父视图的这两个参数是在viewRoot里面getRoot方法得到的,根式图总是充满全局的。下面来看看measure的核心代码:
//final方法,不能被重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}
measure()方法是被用来测量一个view到底多大的。它的实际测量工作是在onMeasure里完成的。它的两个参数widthMeasureSpec和heightMeasureSpec是由父view传递进来的。对于子view来说,大小是由父view和子view共同决定的。
两个很重要的参数:
MeasureSpec
MeasureSpec代表了宽高的尺寸要求,是一个int型的32位参数。前两位代表的是mode后面30位代表的是size。
mode一般分三种:
- UNSPECIFIED 不指定大小;
- EXACTLY 精准大小;
- AT_MOST 最大值模式;
makeMeasureSpec将mode和size打包成int型的MeasureSpec;
LayoutParams
LayoutParams被view用于告诉父布局想被怎样包裹。
- MATCH_PARENT:该view希望和父布局一样大。
- WRAP_CONTENT:该view希望包裹住其内容。
ok。继续往下看onMeasure。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure的方法很简单,就是调用了setMeasuredDimension().这个方法其实就是最终将width和height赋值给了全局变量保存了起来,因此一旦调用了这个方法意味着此view的测量结束。注意注释里提到的一句话:==必须重写setMeasureDimension这个方法,不然会由measure抛出异常。==
继续看getDefaultSize().
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
如果specMode是AT_MOST和EXACTLY,则尺寸就是specSize。而默认的specSize就是父布局传入进来的。
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
最小的建议宽度和高度是由view的bg和设置的大小决定的。
ViewGroup的测量
ViewGroup的测量从MeasureChilderen开始,实际内部是递归调用了MeasureChild(==属性为gone的不测==量),让我们直接看MeasureChild这个方法:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
结合父布局的MeasureSpec和子view的宽高params等,再调用getChildMeasureSpec来调整子view的measureSpec,最后调用子view的measure方法进行处理。那继续看下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的剩余大小。
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) {
// 剩余的大小都给你~
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 剩余的大小可以给你,你自己看吧,不要超过我的大小。
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
.....
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
(childDimension是宽高,padding是边界大小)三个mode其实差不多,简单来说就是:如果发现childDimension是一个具体数值>=0,那么设为exactly,如果是match_parent,resultmode设置为exactly;如果是wrap_content,设为AT_MOST;
ok。至此就是viewGroup的测量。
使用view的getMeasureWidth/Height可以获取view的大小,但是必须在onMeasure流程结束后获取。
View的布局 onLayout
因为layout也是递归结构,所以让我们直接看ViewGroup的layout方法:
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;
}
}
也是个final方法,子类不可继承重写。调用了父类的layout,也就是view的layout方法。
public void layout(int l, int t, int r, int b) {
...
onLayout(changed, l, t, r, b);
...
}
调用了onLayout。而ViewGroup的onLayout是个抽象方法!也就是说,新建一个ViewGroup的子类,必须重写onLayout方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
而view的onLayout是个空方法...it's fine。让我们随便看个例子,frameLayout是如何实现的:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
...
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);
}
}
}
会参考measure过程中计算得到的width和height来安排子view在父view中的显示位置,然后通过各种gravity的switch语句来决定如何放置子view.measure操作完成后得到的是每个view经过测量的measureWidth和measureHeight,layout操作完成后得到的是对每个view位置分配后的left,right等值。
View的draw分析
viewgroup没有重写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;
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);
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;
}
···
}
注释写的很清楚了,首先绘制背景,有必要的话保存图层,然后==绘制内容onDraw==,传递绘制事件,绘制其他的一些小控件之类的。(第二步和第五步可以忽略)ok。我们重点看剩余的四步:
- 1.对view的背景进行绘制drawBackground
- 2.绘制view的内容,也就是onDraw,这个每个控件都不一样,view里面的onDraw是个空实现。
- 3.dispatchView。也就是对当前view的所有子view进行绘制。本身是个空实现,需要控件自身进行重写,比如FrameLayout这样继承了ViewGroup的控件会去重写这个方法,然后在方法里去做drawChild的操作;
- 4.对view的滚动条进行绘制。
好了,到此为止,view的绘制流程到此结束。
网友评论