Android 之美 从0到1 之Android 进阶(二)
在上一章节中《Android 之美 从0到1 之Android 进阶(一)》中我们已经理解了一些View的基本知识并且知道如何自定义View。那么本章节将继续深入理解View,关于View的绘制流程,View的事件分发。刷新机制等等。
在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan blog
本章系《Android 之美 从0到1 – 高手之路》Android 深入理解View的绘制流程。
掌握
- Window是什么?
- View的绘制流程
- View的事件分发机制
- View 与SurfaceView,GLSurfaceView
View的绘制流程
上一文章中我们已经自定义View以及View的三大过程,基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法
在理解View的绘制流程之前我们应该知道这几个类:
- View:最基本的UI组件,表示屏幕上的一个矩形区域。
- Window: 是一个抽象基类,作用于外观用户界面和行为策略表示一个窗口,它包含一个View tree和窗口的layout 参数。View tree的root View可以通过getDecorView得到。还可以设置Window的Content View。其实现类是PhoneWindow。Activity,Dialog,Toast,都包含一个Window,该Window在Activity的attach()函数中
mWindow = new PhoneWindow(this);
创建。 - DecorView:该类是PhoneWindow类的内部类,继承自FrameLayout,它是所有应用窗口的根View,PhoneWindow设置DecorView为应用窗口的根视图。
- PhoneWindow:PhoneWindow对象帮我们创建了一个PhoneWindow内部类DecorView(父类为FrameLayout)窗口顶层视图
- ViewRootImpl:ViewRootImpl是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure、layout、draw)以及我们一些addView()的操作,都是通过ViewRootImpl完成的。
WindowManager:应用程序界面和窗口管理器
在ActivityonCreate
使用的setContentView()就是设置的ContentView,通过LayoutInflater将xml内容布局解析成View树形结构添加到DecorView顶层视图中id为content的FrameLayout父容器上面。那么DecorView是如何绘制的呢?我们分两个步骤来理解:
- DecorView添加到Window的过程
- DecorView的绘制过程
DecorView添加到Window的过程
我们根据下图步骤来解析DecorView添加到Window的过程,以便让我们更容易的理解。
- **Activity初始化:**Activity 启动,关于Activity的创建过程啊或者其他细节,因为不是本篇幅重点故不做详细讨论。我们尽量简化理解View的绘制流程。
PhoneWindow的创建:
Activity对象创建完成后,初始化了PhoneWindow对象,该Window在Activity的attach()函数中mWindow = new PhoneWindow(this);
创建,相关代码块如下:final void attach(Context context, ActivityThread aThread..){ .......... mFragments.attachHost(null /*parent*/); //创建PhoneWindow对象 mWindow = new PhoneWindow(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); .......... }
DecorView添加Window:
ActivityThread.java
类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,因此通过PhoneWindow的setContentView将Activity与Window进行关联了。final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { //获得当前Activity的PhoneWindow对象 r.window = r.activity.getWindow(); //获得当前PhoneWindow内部类DecorView对象 View decor = r.window.getDecorView(); //设置DecorView为可见 decor.setVisibility(View.INVISIBLE); //获取Activity的WindowManager 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) { //标记已添加至Window a.mWindowAdded = true; //添加DecorView到Window wm.addView(decor, l); } }
接着DecorView通过WindowManager设置到ViewRootImpl中,然后就是下面DecorView的绘制流程了。
因此我们知道在Activity的onCreate和onResume方法中调用View.getWidth()和View.getMeasuredHeight()返回值是0,因为View 还没有开始绘制。
View的绘制过程
ViewRootImpl是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure、layout、draw)都是通过ViewRootImpl完成的,
绘制是从根节点开始,对布局树进行 measure 和 draw 。整个 View 树的绘图流程在 ViewRootImpl.java 类的 performTraversals() 函数展开,该函数所做 的工作可简单概括为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),结合DecorView添加至Window过程,整体大概的流程图如下:
那么我们围绕图上过程来分析View的绘制流程,首先我们进入ViewRootImpl.java
中,查看performTraversals
函数,这个函数非常长,View的绘制三大流程将在此展开。
“` private void performTraversals() {
// 缓存DecorView ,因为在下面用的比较多
final View host = mView;
…..
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
“And hey let’s measure once more: width=” + width
+ ” height=” + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
…..
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
//执行绘制操作
performDraw();
}
“`
主要分下面三大步骤。
measure
measure操作主要用于计算视图的大小
在前面文章 Android 之美 从0到1 Android 进阶(一)中我们知道View的MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams共同确定,而对于DecorView是由它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同确定。
在ViewRootImpl的performTraversals方法中,完成了创建DecorView的MeasureSpec的过程,相应的代码片段如下:
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
我们知道Activity的根视图总是全屏的,因为ViewRootImpl 在创建DecorView的MeasureSpec的过程 测量模式是EXACTLY,而Size是windowSize,相应的代码片段如下:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//匹配父容器时,测量模式为MeasureSpec.EXACTLY,测量大小直接为屏幕的大小,也就是充满真个屏幕
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
......
}
return measureSpec;
}
View 的measure过程
measure在performMeasure开始的,该函数在view中定义为final类型,要求子类不能修改。measure()函数中又会调用onMeasure()函数,相应的代码片段如下:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...........
//如果上一次的测量规格和这次不一样,重新测量视图View的大小
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -