View 的加载过程
先附一张视图构成
(参考:https://www.jianshu.com/p/217b205f84b8 ,
https://blog.csdn.net/yanbober/article/details/45970721 )
(参考:https://www.jianshu.com/p/012745b7c1c8 )
从Activity 的 onCreate() 方法执行 setContentView(view) 开始,执行PhoneWindow 的setContentView(view) 方法(基于android api 28)
PhoneWindow 是Window的一个实现,setContentView源码解释如下:将屏幕内容设置为显式视图。该视图被直接放进了屏幕的视图层次结构中。它本身可以成为一个复杂的视图层次。(最后一个单词拼错了,应该是hierarchy)
在PhoneWindow具体实现的setContentView方法如下:(文件路径:D:\android_studio_sdk\sdk\sources\android-28\com\android\internal\policy )
如果第一次设置 mContentParent (ViewGroup) 为空,则执行 installDecor 方法
private void installDecor() {
mForceDecorInstall = false;
//如果DecorView为空就开始设置DecorView
if (mDecor == null) {
//Decor就是在这里创建的,我们看一下这个方法
mDecor = generateDecor(-1);
//获取焦点
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
......
}
generateDecor 方法 直接创建了 DecorView,将其添加到了Window中
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
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();
}
return new DecorView(context, featureId, this, getAttributes());
}
创建完mDecorView 之后,需要显示到界面上。当启动Activity调运完ActivityThread的main方法之后,接着调用ActivityThread类performLaunchActivity来创建要启动的Activity组件,在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;接着Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。
先看下handleResumeActivity方法一个重点,如下:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
......
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
......
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
......
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
......
// 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.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
......
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
......
}
}
调用Activity的makeVisible方法显示我们上面通过setContentView创建的mDecor视图族。所以我们看下Activity的makeVisible方法,如下:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
几个类的职责:
1、Window是一个抽象类,提供了绘制窗口的一组通用API。
2、PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。
3、DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View
4、ViewManager接口定义了一组规则,也就是add、update、remove的操作View接口。
5、WindowManager接口除了实现ViewManager接口定义的方法外,还定义一些与窗口显示相关的方法。
6、WindowManagerImpl为WindowManager的实现类,一般里面的方法都是具体的实现方法,但它不是!
7、WindowManagerGlobal是WindowManagerImpl的具体实现类,类似代理,但没有实现同样的接口,且没有继承关系!☆
8、ViewRoot 对应于ViewRootImpl类,它是链接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
过渡:
在Activity的生命周期中从视图的加载到绘制是从 ActivityThread 的 handleResumeActivity开始的(https://www.jianshu.com/p/4f5f42949262)
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
//Part01
//调用activity.onResume,把activity数据记录更新到ActivityClientRecord
ActivityClientRecord r = performResumeActivity(token, clearHide);
//Part02
if (r != null) {
final Activity a = r.activity;
//activity.mStartedActivity是用来标记启动Activity,有没有带返回值,一般我们startActivity(intent)是否默认是startActivityForResult(intent,-1),默认值是-1,所以这里mStartedActivity = false
boolean willBeVisible = !a.mStartedActivity;
...
//mFinished标记Activity有没有结束,而r.window一开始activity并未赋值给ActivityClientRecord,所以这里为null
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow(); //赋值
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);//默认设置DecorView不可见
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//把当前的DecorView与WindowManager绑定一起
wm.addView(decor, l);
}
...
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//Part03
//标记当前的Activity有没有设置新的配置参数,比如现在手机是横屏的,而之后你转成竖屏,那么这里的newCofig就会被赋值,表示参数改变
if (r.newConfig != null) {
r.tmpConfig.setTo(r.newConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
//然后调用这个方法回调,表示屏幕参数发生了改变
performConfigurationChanged(r.activity, r.tmpConfig);
...
WindowManager.LayoutParams l = r.window.getAttributes();
...//改变之后update更新当前窗口的DecorView
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
//参数没改变
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
//Part04
if (r.activity.mVisibleFromClient) {
//由于前面设置了INVASIBLE,所以现在要把DecorView显示出来了
r.activity.makeVisible();
}
}
//通知ActivityManagerService,Activity完成Resumed
ActivityManagerNative.getDefault().activityResumed(token);
}
关系图解:
当DecorView加载至WindowManager的使用,真正调用的是WindowManagerGlobal.addView()方法。该方法是通过ViewRoot的setView()方法将View传递给WindowManager。ViewRoot实现了View和WindowManager之间的消息传递。
视图的绘制流程
(摘自:https://www.jianshu.com/p/b755d459d0f4)
由前文可知,DecorView是通过WindowManager.addView进行添加的。而具体的实现则为WindowManagerGlobal的addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
...
// ViewRootImpl 初始化
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//ViewRootImpl开始绘制view
root.setView(view, wparams, panelParentView);
...
}
拓展
ViewRootImpl是一个视图层次结构的顶部,它实现了View和WindowManager之间所需要的协议。在WindowManagerGlobal的实现方法中,大部分都是调用了ViewRootImpl。例如addView,removeView,updateViewLayout等
理解
WindowManagerImpl -> WindowManagerGlobal -> ViewRootImpl
ViewRootImpl构造方法
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
...
}
AttachInfo为View里的一个静态内部类
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
}
可以看到viewRootImpl在的构造方法里赋值了,且View与ViewRootImpl绑定在一起了。以后就可以通过view.getViewRootImpl获取到,而在Window也可以获取到,因为Window里有DecorView。
View
public ViewRootImpl getViewRootImpl() {
if (mAttachInfo != null) {
return mAttachInfo.mViewRootImpl;
}
return null;
}
DecorView
private ViewRootImpl getViewRootImpl() {
if (mDecor != null) {
ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
if (viewRootImpl != null) {
return viewRootImpl;
}
}
throw new IllegalStateException("view not added");
}
两个方法都是@hide注解,不能直接调用,需用反射等方法间接调用。
一个View会对应一个ViewRootImpl吗?
测试:
Log.e(TAG, "getViewRootImpl: textView: " + tv.getViewRootImpl() );
Log.e(TAG, "getViewRootImpl: button: " + btn.getViewRootImpl() );
结果:
可以看到,都是同一个对象,共用一个ViewRootImpl。
ViewRootImpl
接着我们看ViewRootImpl的setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
if (mView == null) {
mView = view;
...
//渲染UI
requestLayout();
...
try {
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
mAttachInfo.mRootView = view;
}
}
判断ViewRootImpl的mView是否为空,为空进行赋值。接着调用requestLayout()。表示添加Window之前先完成第一次layout布局过程,以确保收到任何系统时间后重新布局。requestLayout最终会调用performTraversals方法来进行View的绘制。
ViewRootImpl — requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
//判断当前线程是否为UI线程
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
先判断当前的线程是否为UI线程,如果不是抛出异常。
拓展
在子线程里更新UI,会报错吗?此例子不会报错!为什么???
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
new Thread(new Runnable() {
@Override
public void run() {
tv.setText("Hohohong Test");
}
}).start();
}
这与ViewRootImpl的初始化有关,由于在onCreate的时候,此时的View还没有绘制出来,ViewRootImpl也还未初始化,所以更不用去CheckThread。
ViewRootImpl — scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
//实现了Runnable接口
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
scheduleTraversals()中会通过handler去异步调用mTraversalRunnable接口。mChoreographer可以理解为Handler的一个封装类。
ViewRootImpl — doTraversal()
void doTraversal() {
...
performTraversals();
...
}
private void performTraversals() {
......
//测量View的宽高
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//布置View的位置
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
//监听事件
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
//触发OnGlobalLayoutListener的onGlobalLayout()函数
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
......
//渲染View
performDraw();
}
......
}
可以看到doTraversal()最终调用了performTraversals()。这就是渲染UI的核心代码了,此时视图已经被绘制出来了。
关系图
拓展
由于此方法是在onResume调用,所以在onCreate获取视图的宽高是等于0的,因为此时view压根都还没有测量宽高。而在onResume中,由于此方法是异步的,所以在onResume中获取视图的宽高有可能会得到0。
我们如何准确获取视图的宽高呢?
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// TODO Auto-generated method stub
}
});
由于此接口是在View的Measure和Layout之后调用的,所以可以得到视图的宽高了。
总结
1、WindowManager的视图操作,具体是都是通过ViewRootImpl进行操作。
2、在ViewRootImpl未初始化的时候,可以进行子线程UI更新。而它的创建是在activity.handleResumeActivity方法调用,即DecorView被添加到WindowManager的时候
3、ViewRootImpl绘制View的时候会检查当前线程是否为主线程,是的话才可以继续绘制。
PS: ViewRootImpl不仅仅有绘制的功能,它还有事件分发的功能
绘制流程Measure – 测量并设置视图宽 / 高
视图的绘制工作大概分为Measure、Layout和Draw三个主要的流程。而启动这些流程的一个入口则是ViewRootImpl类的performTraversals()。
private void performTraversals() {
......
//最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
//lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
performMeasure(...) -> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(...) -> mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
performDraw() -> draw(fullRedrawNeeded) -> drawSoftware(...) -> mView.draw(canvas);
......
}
该函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个。
ViewRootImpl — 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.
//Window无法调整大小,强制根视图的宽高为Window的宽高,即全屏
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
......
}
return measureSpec;
}
由注释得此MeasureSpec是测量RootView的。由于默认的rootDimension为MATCH_PARENT,所以switch走的是MATCH_PARENT,然后使用MeasureSpec.makeMeasureSpec方法组装一个MeasureSpec,specMode=EXACTLY,specSize=WindowSize,也就是为何根视图是全屏的原因。
对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高了。
View — measure()
View -> ViewGroup -> FrameLayout -> DecorView
//final方法,子类不可重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
//回调onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
这里执行的是View中的measure(子类未重写),measure为整个View树计算实际的大小,然后设置实际的宽高。每个View空间的实际宽高都是由父视图和自身决定的。由于measure为final,View的子类无法重写该方法,因此View子类只能通过重写onMeasure实现自己的测量逻辑,measure方法最终回调了View的onMeasure方法。
measure传入了两个重要测量参数MeasureSpce,详情了解 (https://www.jianshu.com/p/9d550fb8cb30)
View — onMeasure(…)
如果是一个没有子视图的View的话,其执行View的onMeasure(就是如下的方法),而如果当前视图是DecorView的话这里应该首先执行 DecorView 中的 onMeasure 方法,然后执行 FrameLayout 中的onMeasure 方法,然后并未执行根View中的 onMeasure,因为FrameLayout的 onMeasure方法中并没有 super.onMeasure(…)
//View的onMeasure默认实现方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置DecorView的宽高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
真正设置View的宽高的即setMeasuredDimension方法,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值。如果想使用现成的View想通过getMeasuredWidth() 和 getMeasuredHeight() 方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
如果不调用此方法的话会出现异常
xxView#onMeasure() did not set the measured dimension by calling setMeasuredDimension()
默认系统会给我们测量一个默认的宽高,具体如下
//获取建议的最小宽度
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//获取建议的最小高度
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//通过MeasureSpec解析获取mode与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,这就是系统默认的规格。
DecorView测量子视图
到这里一次简单的View视图Measure已经完成。DecorView作为根视图,系统默认设置全屏显示。而DecorView也是一个父视图,那DecorView如何测量自己的所有子视图呢?接着上面,在执行 View中的measure 方法的时候,onMeasure就执行到了DecorView 中,也执行到了 FrameLayout -> onMeasure中
这里要再次明确视图关系:View -> ViewGroup -> FrameLayout -> DecorView
以下三个方法是在 ViewGroup 中
ViewGroup是一个抽象类,需要具体的视图去实现,比如这里的
FrameLayout.onMeasure --> ViewGroup.measureChildWithMargins(…) -->child.measure(…)
其他具体实现类 LinearLayout、RelativeLayout、ConstraintLayout
1、LinearLayout.onMeasure --> LinearLayout.measureVertical / measureHorizontal --> LinearLayout.measureChildBeforeLayout --> ViewGroup.measureChildWithMargins --> child.measure
2、RelativeLayout.onMeasure --> RelativeLayout.measureChildHorizontal --> child.measure
3、AbsoluteLayout.onMeasure --> ViewGroup.measureChildren --> ViewGroup.measureChild --> child.measure
4、ConstraintLayout.onMeasure --> child.measure
此时有3个重要的方法measureChildren, measureChild, measureChildWithMargins
1、measureChildren内部循环调用measureChild
2、measureChild和measureChildWithMargins内部调用了子视图的measure,而两者的区别就在于是否要将margin作子自视图的大小
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
//获取子视图的LayoutParams
final LayoutParams lp = child.getLayoutParams();
//区别之处:第二入参
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//获取子视图的LayoutParams
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);
//调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
两个方法就是对父视图提供的measureSpec参数结合自身的LayoutParams参数进行了调整,然后再来调用child.measure()方法,具体通过方法getChildMeasureSpec来进行参数调整。
ViewGroup — 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;
//依据当前Parent的Mode进行switch分支逻辑
switch (specMode) {
// Parent has imposed an exact size on us
//默认Root View的Mode就是EXACTLY
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//如果child的layout_widh属性在xml或者java中给予具体大于等于0的数值
//设置child的size为真实layout_widh属性值,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果child的layout_widh属性在xml或者java中给予MATCH_PARENT
// Child wants to be our size. So be it.
//设置child的size为size,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果child的layout_widh属性在xml或者java中给予WRAP_CONTENT
//设置child的size为size,mode为AT_MOST
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
......
//其他Mode分支类似
}
//将mode与size通过MeasureSpec方法整合为32位整数返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec的逻辑是通过将父视图提供的MeasureSpec参数得到spceMode和spectSize,然后通过计算出来的size以及子View的childDimension(layout_width或layout_height)来计算出ChildMeasureSpec,子视图通过ChildMeasureSpec调用measure函数,计算子视图的宽高。
总结
测量(Measure)是从顶层父View向子View递归调用view.measure的方法。
1、MeasureSpec(View的内部类)作为测量视图的重要数据,不仅包含了size还有mode,详细了解 https://www.jianshu.com/p/9d550fb8cb30
2、顶层DecorView所需的MeasureSpec是由ViewRootImpl中的getRootMeasureSpect提供的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)
3、ViewGroup类提供了measureChildren,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算
4、View的布局大小由父View和子View共同决定。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高了
5、使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
(重写 onMeasure 的例子:https://blog.csdn.net/haibo_bear/article/details/52223915 )
绘制流程Layout — 确定视图的位置
(摘自:https://www.jianshu.com/p/9501944a723a )
关系图
前面是View的测量(Measure)过程。接着我们开始聊布局(Layout),以下是起始ViewRootImpl 的 performTraversals方法。
ViewRootImpl – performTraversals
private void performTraversals() {
......
performMeasure(...) -> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
// 以下重点
performLayout(...) -> mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
performDraw() -> draw(fullRedrawNeeded) -> drawSoftware(...) -> mView.draw(canvas);
......
}
可以看见layout方法接收了4个参数,分别表示左、上、右、下。从上可以看出mView(DecorView)的左上都为0,右为width,下为height。
Layout 过程图
视图层次:View --> ViewGroup --> FrameLayout --> DecorView
mView即DecorView(FrameLayout)是ViewGroup的子类,我们来看下ViewGroup的layout方法。注意 ViewGroup中重写了 View中的layout方法并设置为final类型,子类不可重写,且通过super.layout传到View的layout方法中,而View中的measure方法是final类型不可为其他子类重写的,包括ViewGroup
View — layout
public void layout(int l, int t, int r, int b) {
......
//实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
//判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//需要重新layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//回调onLayout
onLayout(changed, l, t, r, b);
......
}
......
}
//空方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
layout完成后,随即调动用onLayout。这里与measure类似。View的onlayout是一个空方法。
ViewGroup — onLayout
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
ViewGrpup的onLayout为抽象方法,也就意味着所有ViewGroup的子类都得实现onLayout方法。重写onLayout的目的就是安排其children在父View的具体位置。重写onLayout通常做法就是写一个for循环调用每一个子视图的layout(l,t,r,b)方法,通过传入不同的数据来排列所有的子视图。(这里可以参考onMeasure)
FrameLayout — onLayout
在DecorView中
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
......
}
在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) {
//获取子视图的个数
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;
//判断水平方向,给子视图的left赋值
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;
}
//判断竖直方向,给子视图top赋值
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);
}
}
}
从上面对DecorView(FrameLayout)的onLaout分析可看出。DecorView通过获取子视图(measure)的宽高,且通过gravity计算出子视图对应的左、上坐标,从而得出右、下左标。从而实现一个子视图的布局。
当然各种ViewGroup有各种onLayout的实现,本文这里简单讲解FrameLayout。
拓展
我们以前遇到getWidth()、getHeight()和getMeasureWidth()、getMeasureHeight()傻傻不能分清楚,那他们的区别是什么呢?
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
public final int getLeft() {
return mLeft;
}
public final int getRight() {
return mRight;
}
public final int getTop() {
return mTop;
}
public final int getBottom() {
return mBottom;
}
getMeasureWith()、getMeasureHeight()我们的上文已经说过了,必须在onMeasure之后才有效。而getWith()和getHeight()要在onLayout之后才有效。(例子:https://blog.csdn.net/haibo_bear/article/details/52223915 )
总结
1、View.layout可重写,ViewGroup.layout不可重写。ViewGroup.onlayout为抽象方法,子类必须实现该方法。
2、measure操作后得到的是measureWidth和measureHeight;layout操作后得到的是坐标left,top,right,bottom,当然这些值是相对于父View来说的
3、getWith()和getHeight()方法必须在onLayout后调用才有值。
绘制流程Draw —
(摘自:https://www.jianshu.com/p/7eee148dec11 、
https://www.cnblogs.com/xinmengwuheng/p/7070092.html )
关系图、结构图
上面我们对View的测量(Measure)进行讲解了。接着我们开始聊绘制(Draw),以下是我们熟悉的performTraversals方法。
private void performTraversals() {
......
performMeasure(...) -> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(...) -> mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
// 以下重点
performDraw() -> draw(fullRedrawNeeded) -> drawSoftware(...) -> mView.draw(canvas);
......
}
其中在 drawSoftware 中 在执行 mView.draw(canvas) 之前会 生成 canvas对象
canvas = mSurface.lockCanvas(dirty);
我们看出ViewRootImpl创建一个canvas,然后mView(DecorView)调用draw方法并传入canvas,此方法执行具体的绘制工作。与Measure和Layout类似的需要递归绘制。
理解图
View — draw
接着上面代码,从DecorView执行 draw开始,代码如下:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
由于ViewGroup没有重写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
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
......
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
......
}
整个的绘制流程分成6步,通过注释可以知道第2步和第5步(skip step 2 & 5 if possible (common case))可以忽略跳过,我们对剩余4步就行分析。
View — draw — drawBackground①
第一步:对View的背景进行绘制
private void drawBackground(Canvas canvas) {
//获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
final Drawable background = mBackground;
......
//根据layout过程确定的View位置来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
......
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
......
}
draw方法通过调运drawBackground(canvas);方法实现了背景绘制。
View — draw — onDraw ③
第三步:对View的内容进行绘制
protected void onDraw(Canvas canvas) {
}
ViewGroup没有重写该方法,view的方法也紧紧是一个空方法而已。大家都知道不同的View是显示不同的内容的,所以这块必须是子类去实现具体逻辑。
View — draw — dispatchDraw ④
第四步:对当前View的所有子View进行绘制
protected void dispatchDraw(Canvas canvas) {
}
View的dispatchDraw()方法是一个空方法。这个我们也比较好理解,本身View自身就没有所谓的子视图,而拥有子视图的就是ViewGroup!所以我们可以看下ViewGroup的dispatchDraw
ViewGroup – dispatchDraw
@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);
}
}
......
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可见,Viewgroup重写了dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法。而drawChild方法内部是直接由子视图调用draw()方法。
View — draw ---- onDrawForeground
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
......
foreground.draw(canvas);
}
该方法绘制装饰(滚动指示器、滚动条、和前景)
TextView 的绘制过程可参考:https://www.cnblogs.com/bvin/p/5370490.html
总结
(摘自:https://www.cnblogs.com/xinmengwuheng/p/7070092.html )
单一View的绘制,只需要绘制自身
ViewGroup的绘制图解如下:
拓展
1、View 中有一个特殊的方法:setWillNotDraw()
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
该方法用于设置 WILL_NOT_DRAW 标记位,该标记位的作用是:当一个View不需要绘制内容时,系统进行相应优化 。默认情况下:View 不启用该标记位(设置为true);ViewGroup 默认启用(设置为false)
应用场景:
① setWillNotDraw参数设置为true:当自定义View继承自 ViewGroup 、且本身并不具备任何绘制时,设置为 true 后,系统会进行相应的优化。
② setWillNotDraw参数设置为false:当自定义View继承自 ViewGroup 、且需要绘制内容时,那么设置为 false,来关闭 WILL_NOT_DRAW 这个标记位。
2、我们经常在自定义View的时候会遇到两种方法invalidate和postinvalidate。我们来看看两个方法与视图的绘制有什么样的联系呢?
invalidate方法源码分析,由于ViewGroup并没有重写该方法,所以我们直接看View的invalidate
// invalidate只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
//实质还是调用invalidateInternal方法
invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}
//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View
public void invalidate(int l, int t, int r, int b) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
//实质还是调运invalidateInternal方法
invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
}
//public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View
public void invalidate() {
//invalidate的实质还是调运invalidateInternal方法
invalidate(true);
}
//default的权限,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View
void invalidate(boolean invalidateCache) {
//实质还是调运invalidateInternal方法
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
//这是所有invalidate的终极调运方法!!!!!!
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
......
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
//设置刷新区域
damage.set(l, t, r, b);
//传递调运Parent ViewGroup的invalidateChild方法
p.invalidateChild(this, damage);
}
......
}
View的invalidate方法最终调动invalidateInternal方法。而invalidateInternal方法是将要刷新的区域传递给父视图,并调用父视图的invalidateChild。
ViewGroup — invalidateChild
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
......
do {
......
//循环层层上级调运,直到ViewRootImpl会返回null
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
这个过程不断的向上寻找父亲视图,当父视图为空时才停止。所以我们可以联想到根视图的ViewRootImpl
ViewRootImpl — invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
......
//View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法
invalidate --> scheduleTraversals();
......
return null;
}
返回为空。刚好符合上面的循环!接着我们看scheduleTraversals这个方法是不是感觉很熟悉呢?这就是前面ViewRootImpl正准备调用绘制View视图的代码。
void scheduleTraversals() {
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
//实现了Runnable接口
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
performTraversals();
}
private void performTraversals() {
//测量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//布局
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
//绘制
mView.draw(canvas);
}
这样一组看下来就很清晰了。View调用invalidate方法,其实是层层往上递,直到传递到ViewRootImpl后出发sceheduleTraversals方法,然后整个View树开始进行重绘制任务。
理解图:
postInvalidate方法源码分析
上面也说道invalidate方法只能在UI线程中执行,其他需要postInvalidate方法
View — postInvalidate
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
//核心,实质就是调运了ViewRootImpl.dispatchInvalidateDelayed方法
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
此方法必须是在视图已经绑定到Window才能使用,即attachInfo是否为空。随后调用ViewRootImpl的dispatchinvalidateDelayed
ViewRootImpl — dispatchInvalidateDelayed
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
Handler乱入!此时ViewrootImpl类的Handler发送了一条MSG_INVALIDATE消息。哪里接收这个消息呢?
ViewRootImpl
final class ViewRootHandler extends Handler {
public void handleMessage(Message msg) {
......
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
......
}
......
}
}
实质上还是在UI线程中调用了View的invalidate()方法。
postInvalidate是在子线程中发消息,UI线程接收消息并刷新UI。
常见的引起invalidate方法操作的原因主要有:
1、直接调用invalidate方法.请求重新draw,但只会绘制调用者本身
2、触发setSelection方法。请求重新draw,但只会绘制调用者本身。
3、触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE / VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。
4、触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。
5、触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。
requestLayout方法源码分析
和invalidate类似,层层往上传递。
public void requestLayout() {
......
if (mParent != null && !mParent.isLayoutRequested()) {
//由此向ViewParent请求布局
//从这个View开始向上一直requestLayout,最终到达ViewRootImpl的requestLayout
mParent.requestLayout();
}
......
}
获取父类对象,调用requestlayout(),最后到达ViewRootImpl
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//View调运requestLayout最终层层上传到ViewRootImpl后最终触发了该方法
scheduleTraversals();
}
}
与invalidate是不是很像呢?reqeustLayout()方法会调用measure和layout过程,不会调用draw过程,也不会重新绘制任何View,包括调用者本身。而invalidate则绘制draw为主。
(以下摘自:https://blog.csdn.net/zoky_ze/article/details/54892971 )
ViewRootImpl的requestLayout方法,可以看到mLayoutRequested变true了,然后触发了scheduleTraversals 方法,requestLayout与invalidate的调用过程类似,只是设置的标志位不同,导致View的绘制流程中执行方法不同而已。
我们可以简单的认为mLayoutRequested为true会触发perfomMeasure(内部会调用onMeasure)和performLayout(内部会调用onLayout)。然后在performDraw内部draw的过程中发现mDirty为空,所以onDraw不会被调用,不重绘。
这么看来requestLayout不会导致onDraw调用了?
也不见得,我们知道requestLayout会导致perfomMeasure和performLayout,如果在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate。代码在View的setFrame中,这个会在layout时被调用。
public void layout(int l, int t, int r, int b) {
......
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
.....
}
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
此处一定会执行 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;
......
// Invalidate our old position
invalidate(sizeChanged);
}
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
invalidateInternal 该方法就是前面提到的 invalidate 实际执行的方法。
(以下摘自:https://blog.csdn.net/lhl5281/article/details/80136240 )
在继承至ViewGroup的自定义控件中,invalidate是默认不重新绘制子view的。
有以下两种方法来触发重新绘制的过程:
方法一:在构造函数中调用setWillNotDraw(false);
方法二:给ViewGroup设置背景。调用setBackground。
通过源码分析,两个方法都是一个逻辑:需要将ViewGroup的dirtyOpaque设置为false.
这样其父类的draw方法会调用ondraw执行绘制过程。
ondraw方法():
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE
setWillNotDraw(false) 触发重新绘制的过程会一直执行,需要setWillNotDraw(true)来清除改标志位。
setBackBackground也是类似,改变prviateFlags值,触发ondraw重新绘制。
(invalidate 、requestLayout 和 Choroegrapher 分析可见: https://www.cnblogs.com/tiger-wang-ms/p/6592189.html )