一、DecorView、Window、ViewRootImpl 等概念
View的三大流程:measure、layout、draw
1、 ViewRootImpl
连接 WindowManager 和 DecorView
View 三大流程都是通过 ViewRootImpl 完成的
-
在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,
同时会创建 ViewRootImpl 对象,将 ViewRootImp 与 DecorView 建立关联位于:WindowManagerGlobal.addView()
root = new ViewRootImpl(view.getContext(), display); root.setView(view, wparams, panelParentView);
-
View的绘制流程, 从 ViewRoot 的 performTraversals() 方法开始,经过 measure、layout、draw
将一个View绘制出来measure:测量 View的宽高
layout:确定 View在父容器中的位置
draw:将 View绘制在屏幕上 -
performTraversals() 会依次调用 performMeasure、performLayout、performDraw 三个方法
这三个方法分别完成 顶级View 的 measure、layout、draw三大流程。
performMeasure -> measure -> onMeasure
performLayout -> layout -> onLayout
performDraw -> draw -> dispatchDraw -> onDraw -
顶级View 在 onMeasure 方法中,会对所有子元素进行 measure 过程
顶级View 在 onLayout 方法中,会对所有子元素进行 layout 过程
顶级View 在 onDraw 方法中,会 通过 dispatchDraw 传递到子元素 进行 draw 过程
动作 | 结果 |
---|---|
measure 完成 | getMeasuredWidth / getMeasuredHeight 可拿到View宽高 |
layout 完成 | getTop、getBottom、getLeft、getRight 可拿到View的四个顶点位置 getWidth、getHeight 可拿到View的最终宽高 |
draw 完成 | View的内容显示在屏幕上 |
View绘制过程
2、 DecorView
顶级View
内部包含一个竖直方向的LinearLayout,其中分为上下两部分
上部分是标题栏
下面是内容栏 FrameLayout (id 为 android.R.id.content)
Activity.setContentView,设置的 View,会被添加到内容栏
内容栏id:android.R.id.content
3、 Window
窗口,Window是View的管理者,唯一的实现类是 PhoneWindow
WindowManager 是访问 Window 的入口。
Window 的具体实现在 WindowManagerService 中
Android中所有试图都是通过Window 呈现
事件传递,由 Activity -> Window -> DecorView -> View
二、MeasureSpec 概念
-
MeasureSpec 代表
一个 32 位的int 值
高 2 位比代表 SpecMode
低 30 位代表 SpecSize -
SpecMode 测量模式 ,int值
-
SpecSize 某种测量模式下的尺寸,int 值
MeasureSpec 将 SpecMode 、SpecSize 拼成一个 int 值,来避免过多对象内存分配
-
打包、解包的方法:
打包:public static int makeMeasureSpec(int size, int mode);
解包:
public static int getMode(int measureSpec); public static int getSize(int measureSpec);
1、SpecMode 有三类:
-
UNSPECIFIED
父容器不限制 View 的尺寸,要多大给多大。
用于【系统内部】,表示一种测量状态。 -
EXACTLY
父容器检测出 View 所需要的精确大小,此时 View最终大小就是 SpecSize 所指定的值
对应 LayoutParams 中的 match_parent 和 具体的数值 -
AT_MOST
父容器指定一个可用大小 SpecSize,View大小不能大于这个值,具体值要看不同View的具体实现。
对应 LayoutParams 中的 wrap_content根据【ViewGroup.getChildMeasureSpec() 源码】推导
普通View的 MeasureSpec 创建规则:
父容器的 MeasureSpec 和 View 的 LayoutParams 共同确定View的MeasureSpecchild
LayoutParamsparentSpecMode
EXACTLYparentSpecMode
AT_MOSTparentSpecMode
UNSPECIFIEDdp/px EXACTLY
childSizeEXACTLY
childSizeEXACTLY
childSizematch_parent EXACTLY
parentSizeAT_MOST
parentSizeUNSPECIFIED
API<23?0:parentSizewrap_content AT_MOST
parentSizeAT_MOST
parentSizeUNSPECIFIED
API<23?0:parentSize由上表得出结论:
View的 LayoutParams有确切值(dp/px)时,View的测量结果就是该确切值
View的LayoutParams为 match_parent时,View的测量结果是【父容器的剩余尺寸】
View的LayoutParams为 wrap_content时,View的测量结果是【父容器的剩余尺寸】自定义View时,需要对 SpecMode 为 AT_MOST 也就是 wrap_content 的情况做计算处理
否则自定义View 在布局中使用 wrap_content 时,会撑满父容器
源码 ViewGroup.measureChildWithMargins() -> ViewGroup.getChildMeasureSpec():
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //父容器的 specMode int specMode = MeasureSpec.getMode(spec); //父容器的 specSize int specSize = MeasureSpec.getSize(spec); // 父容器减去边距后的剩余 specSize, 也就是父容器的剩余尺寸 int size = Math.max(0, specSize - padding); int resultSize = 0;//子View的 尺寸 int resultMode = 0;//子View的 specMode switch (specMode) { case MeasureSpec.EXACTLY: //父容器EXACTLY,dp/px/match_parent if (childDimension >= 0) { //子View,dp/px resultSize = childDimension;//子View 测量尺寸为 LayoutParams中的dp/px resultMode = MeasureSpec.EXACTLY;//子View Mode为 EXACTLY } else if (childDimension == LayoutParams.MATCH_PARENT) {//子View match_parent resultSize = size;//子View测量尺寸为父容器剩余尺寸 resultMode = MeasureSpec.EXACTLY;//子View Mode为 EXACTLY } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子View wrap_content resultSize = size;//子View 测量尺寸为父容器剩余尺寸 resultMode = MeasureSpec.AT_MOST;//子View Mode AT_MOST } break; case MeasureSpec.AT_MOST://父容器AT_MOST,wrap_content if (childDimension >= 0) {//子View,dp/px resultSize = childDimension;//子View测量尺寸为LayoutParams中的dp/px resultMode = MeasureSpec.EXACTLY;//子View Mode为 EXACTLY } else if (childDimension == LayoutParams.MATCH_PARENT) {//子View match_parent resultSize = size;//子View 测量尺寸为父容器剩余尺寸 resultMode = MeasureSpec.AT_MOST;//子View Mode为 AT_MOST } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子View wrap_content resultSize = size;//子View 测量尺寸为父容器剩余尺寸 resultMode = MeasureSpec.AT_MOST;//子View Mode 为 AT_MOST } break; case MeasureSpec.UNSPECIFIED://父容器UNSPECIFIED if (childDimension >= 0) {//子View,dp/px resultSize = childDimension;//子View测量尺寸为LayoutParams中的dp/px resultMode = MeasureSpec.EXACTLY;//子View Mode为 EXACTLY } else if (childDimension == LayoutParams.MATCH_PARENT) {//子View match_parent //View.sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M; // 5.0以下,resultSize 为0,5.0及以上为父容器剩余尺寸 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED;//子View Mode为 UNSPECIFIED } else if (childDimension == LayoutParams.WRAP_CONTENT) {//子View wrap_content //View.sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M; resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED;//子View Mode为 UNSPECIFIED } break; } //把子View的 specSize 和 specMode 打包成 MeasureSpec 返回 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
三、MeasureSpec 和 LayoutParams 的对应关系
-
View 测量时,系统会将 LayoutParams 在父容器的约束下转换成 对应的 MeasureSpec
再根据这个 MeasureSpec 来确定 View测量后的宽高 -
普通View 的 MeasureSpec 是由 LayoutParams 和父容器的 MeasureSpec 共同决定的,从而进一步决定View 的宽高。
-
对于顶级View – DecorView 与普通 View略有不同
DecorView 的 MeasureSpec 由 窗口的尺寸 和 DecorView 的 LayoutParams 共同决定
-
MeasureSpec一旦确定后,onMeasure中就可以确定View 的测量宽高
四、View的工作流程,measure过程、layout过程、draw过程
一般情况下 measure 完成后,通过 getMeasuredWidth / Height 就可以正确获取View的测量宽高
极端情况下,系统需要多次 measure 才能确定最终测量宽高,此时 onMeasure中拿到的测量宽高可能不准确。
比较好的习惯是,在 onLayout 中去获取View的测量宽高/最终宽高
1、 View的 measure过程
由View的 measure 方法完成,measure方法是 final 类型方法,重写 onMeasure来测量
-
View.onMeasure() 源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //setMeasuredDimension 方法,会设置View 的 宽/高值 setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } /** * 返回的大小就是 measureSpec 中的 specSize * specSize 就是View 测量后的大小 */ public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec);//取出specSize switch (specMode) { case MeasureSpec.UNSPECIFIED: //UNSPECIFIED 一般用于系统内部的测量过程 //size 是 getSuggestedMinimumWidth()/getSuggestedMinimumHeight()返回的 result = size; break; case MeasureSpec.AT_MOST: // wrap_content case MeasureSpec.EXACTLY: //明确大小 和 match_parent result = specSize; // 就是specSize 的值 break; } return result; }
getSuggestedMinimumWidth()
View 没背景, 那么View的宽度为 mMinWidth 对应 android:minWidth 属性 的值
如果 这个属性 不指定,那么 mMinWidth 就默认为 0View 有背景,View的宽度为 max(mMinWidth, mBackground.getMinimumWidth())
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); } /* Drawable.getMinimumWidth() * 返回 Drawable 的原始宽度,如果Drawable 没有原始宽度,就返回 0 * ShapDrawable 没有原始宽高;BitmapDrawable 有原始宽高 */ public int getMinimumWidth() { final int intrinsicWidth = getIntrinsicWidth(); return intrinsicWidth > 0 ? intrinsicWidth : 0; }
-
继承 View 的自定义控件,需要重写 onMeasure 方法,并设置 wrap_content 时的自身大小
否则在布局中使用 wrap_content 就相当于使用 match_parent。原因:
如果View在布局中使用 wrap_content,它的 specMode 是 AT_MOST 模式。
此模式下,它的宽高等于 specSize,这种情况下 View的 specSize 是parentSize也就是父容器剩余空间大小解决方式:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if(widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, heightSpecSize); } else if(heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mHeight); } }
给View 指定一个默认的内部宽高(mWidth和mHeight),并在wrap_content 时设置此宽高
2、 ViewGroup的 measure 过程
ViewGroup 完成自己的measure过程之外,还要遍历调用所有子元素的measure方法。
各个子元素再递归执行这个过程。
-
ViewGroup 的 measureChild
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); //根据ViewGroup的 MeasureSpec 和 child 的 LayoutParams 生成 child的 MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); //根据child的 MeasureSpec 测量 child 尺寸 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
-
Activity 中获取View尺寸
View的measure过程 与 Activity 的生命周期方法不同步,
在Activity生命周期方法中获取View尺寸可能是01)Activity/View#onWindowFocusChanged
View已初始化完毕,宽高已经准备好了。public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }
2)view.post(runnable)
View.post() 将 一个 runnable 消息塞到UI线程消息队列尾部
protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
3)使用 ViewTreeObserver 的回调
protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
4)手动 measure 获取View宽高
view.measure(int widthMeasureSpec, int heightMeasureSpec)
>> View.LayoutParams == match_parent,无法获取。
measure方法,依赖父容器的MeasureSpec,此时父容器也可能还没有绘制完毕>>View.LayoutParams 值 为具体 dp/px
假设 宽高100px:int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY); view.measure(widthMeasureSpec, heightMeasureSpec);
>>View.LayoutParams == wrap_content
MeasureSpec为32位,其中高2位为specMode,低30位为specSize
specSize最大值为 30个1 即 2^30 - 1 , 也就是 (1<<30)-1//这里表示用View理论上能支持的最大specSize 去构造 MesureSpec int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<< 30) - 1, MeasureSpec.AT_MOST); int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<< 30) - 1, MeasureSpec.AT_MOST); view.measure(widthMeasureSpec, heightMeasureSpec);
-
关于View的 measure 网络上的 错误用法
违背了系统内的 MeasureSpec 实现规范,也不能保证一定能measure 出正确结果。i. 错误一
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED); int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED); view.measure(widthMeasureSpec, heightMeasureSpec);
ii. 错误二
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
3、 layout过程
layout()方法:确定View本身的位置
onLayout()方法:确定所有子元素的位置
当ViewGroup 调用layout()确定自身位置后,
再调用onLayout 遍历子元素、并调用子元素的layout()方法。
子元素调用layout()确认自身位置后,又会继续调用 onLayout去遍历孙元素。
这样层层传递完成整个View树的layout过程
-
View的 getMeasuredWidth() 、 getWidth() 获取宽度区别
默认,getMeasuredWidth() == getWidth()
区别:getMeasuredWidth() 在 measure过程赋值,getWidth()是 layout过程赋值重写了 layout()方法,可能导致 测量宽度 与 最终宽度不同
public void layout(int l, int t, int r, int b) { super.layout(l, t, r + 100, b + 100); }
此时,最终宽/高 比 测量宽/高 大100px
layout过程
4、 draw过程
将View 绘制在屏幕上
-
1)绘制背景 drawBackground(canvas)
-
2)绘制自己 onDraw(canvas)
-
3)绘制 children dispatchDraw(canvas)
View的 draw 过程,通过 dispatchDraw 来传递
dispatchDraw 遍历调用所有子元素的 draw 方法
draw 中 再调用 dispatchDraw,层层传递 -
4)绘制装饰 onDrawScrollBars(canvas)
public void draw(Canvas canvas) { ... // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas);//绘制背景 } // skip step 2 & 5 if possible (common case) ... if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas);//绘制自身内容 // Step 4, draw the children dispatchDraw(canvas);//分发绘制children,内部会调用child.draw(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; } } public void onDrawForeground(Canvas canvas) { onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); }
draw过程
draw -> drawBackground绘制背景 -> onDraw绘制自身
-> dispatchDraw遍历调用【子元素draw方法(内部子元素的 draw过程)】 -> onDrawScrollBars 绘制装饰
四、自定义View
1、 自定义View的分类
-
1)继承 View 重写 onDraw
这种方式,需要自己支持 wrap_content,并且 padding 也需自己处理protected void onDraw(Canvas canvas) { super.onDraw(canvas); final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); int width = getWidth() - paddingLeft - paddingRight; int height = getHeight() - paddingTop - paddingBottom; int radius = Math.min(width, height) / 2; canvas.drawCircle(paddingLeft + widt/2, paddingTop + height/2, radius, mPaint); } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200, 200); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(200, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, 200); } }
-
2)继承 ViewGroup 派生特殊的 Layout
实现一种新的容器布局。
需要处理ViewGroup的 measure 和 layout 过程、同时需要处理子元素的 measure 和 layout 过程@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measureWidth = 0; int measureHeight = 0; final int childCount = getChildCount(); //测量子元素尺寸 measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measureWidth = childView.getMeasuredWidth() * childCount; measureHeight = childView.getMeasuredHeight(); setMeasuredDimension(measureWidth, measureHeight); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measureHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpecSize, measureHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measureWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measureWidth, heightSpecSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } }
-
3)继承特定的View (比如 TextView)
用于扩展已有的View的功能
不需要自己支持 wrap_content 和 padding 等 -
4)继承特定的 ViewGroup(比如 LienarLayout)
扩展特定ViewGroup(比如 LienarLayout)的功能。
不需要自己处理ViewGroup的测量和布局过程
自定义View的方式
方式 | 特点 |
---|---|
继承 View 重写 onDraw | 需要实现 AT_MOST模式(wrap_content)的 measure过程 需处理 padding |
继承 ViewGroup | 需要处理 ViewGroup的 measure 和 layout 过程 需要处理子元素的 measure 和 layout 过程 |
继承特定的View(比如 TextView) | 不需处理 AT_MOST模式(wrap_content)的 measure过程 和 padding 等 |
继承特定的 ViewGroup(比如 LienarLayout) | 不需处理 ViewGroup 的 measure 和 layout 过程 |
2、 需要注意的事项
-
1)让View 支持 wrap_content
直接继承View 或者 ViewGroup 的控件,如果不在 onMeasure 中 对wrap_content 特殊处理,
那么当父布局中使用wrap_content时,实际上会使用父容器的剩余尺寸作为View的尺寸。
达不到wrap_content所要的效果。 -
2)让View 支持 padding
直接继承View的控件,如果不在 draw 方法中处理 padding,
那么 padding 属性无法起作用。直接继承ViewGroup 的控件,需要在 onMeasure 和 onLayout 中考虑 padding 和子元素的 margin
不然会导致 padding 和 子元素的 margin 失效。 -
3)不要在 View 中使用 Handler,没有必要。
因为View 内部本身有提供 post 系列方法,可替代 Handler 的作用。 -
4)View 中如果有线程池或者动画,需要及时停止。View#onDetachedFromWindow()
onDetachedFromWindow()停止线程和动画。
View依赖的Activity退出、或者当前View被remove时,onDetachedFromWindow()会被调用。
否则,有可能造成内存泄露。对应的方法是 onAttachedToWindow(),View依赖的Activity启动时 View#onAttachedToWindow()会被调用。
-
5)View带有滑动嵌套情形时,需要处理好滑动冲突
onInterceptTouchEvent(event) 外部拦截、内部拦截,两种解决冲突的方式。
3、View绘制 双缓冲 实现
缓冲:系统会在 onDraw 执行完之后,才将数据交给GPU处理展示
双缓冲:创建一个 Canvas 和对应的 Bitmap 提前绘制在 Bitmap上,然后在 onDraw 方法里,
默认的 Canvas 通过 drawBitmap 画刚才new 的那个 Bitmap
推荐阅读:
《Android开发艺术探索》第四章 View的工作原理
《Android开发艺术探索》第八章 理解Window和WindowManager
Android自定义控件三部曲文章索引