1.基本概念
1.1 ViewRoot
ViewRoot对应于ViewRootlmpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView 添加到PhoneWindow 中,同时会创建ViewRootlmpl 对象,并将ViewRootlmpl对象和DecorView建立关联
将DecorView添加到窗口视图
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
获得ViewRootImpl对象root
root = new ViewRootImpl(view.getContext(), display);
将传进来的参数DecorView设置到root中
root.setView(view, wparams, panelParentView);
View的绘制流程是从ViewRoot的performTraversals 方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。
1.2 DecorView
DecorView是顶级View,包含了一个竖直方向的LinearLayout,上面是标题栏,下面是内容栏,在Activity中通过setContentView进行设置。View层的事件都先经过DecorView,然后才传给我们的View。
获得content:ViewGroup content = findViewById(id为android.R.id.content)
设置View: Content.getChildAt(0);
1.3 MeasureSpec
在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,MeasureSpec 是一个32位int值,高2位SpecMode为测量模式,低30位SpecSize是测量模式下的规格大小;MeasureSpec通过将SpecMode和SpecSize 打包成一个int值来避免过多的内存分配,为了方便操作,其提供了打包和解包方法。一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize;
2.View的工作流程
2.1 View的measure过程:Measure()方法—onMeasure()—getDefaultSize()
- 直接继承View的自定义控件需要重写onMeasure并且设置wrap_content时的自身大小,否则在布局中使用wrap_content等同于match_parent
@Override
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);
}
}
- ViewGroup的measure流程
自己的measure()—子元素的measure()—measure方法中调用的是measureChildren( ),measureChild方法取出子元素的LayoutParams 然后通过getChildMeasureSpec创建子元素的MeasureSpec,传递给View
自定义View Measure过程
2.2 layout过程
ViewGroup用来确定子元素的位子,ViewGroup确定之后在onLayout中会遍历所有的子元素并调用其layout方法,在layout中onLayout又被调用
流程:首先设定View的四个顶点,View在父容器位置确定,接着onLayout被调用,确定子元素的位置。onLayout方法和具体布局有关 View和ViewGroup都没有实现。在View的默认实现中,View的测量宽高和最终宽高是相等的,只不过测量宽高形成于measure过程,而最终宽高形成于layout过程
2.3 draw过程
Draw过程将View绘制到屏幕上,步骤如下(从顶级View到view遍历去做):
- 绘制背景:background.draw(canvas);
- 绘制自己:onDraw();
- 绘制children:dispatchDraw;
- 绘制装饰:onDrawScrollBars
3.自定义View
分类:
- 继承view重写onDraw方法
重写onDraw需要自己支持wrap_content ,并且padding也需要处理 - 继承ViewGroup派生特殊的Layout
主要用于实现自定义布局 - 继承特定的View(比如TextView)
一般用于扩展已有的View的功能 不需要处理wrap_content 和padding - 继承特殊的ViewGroup(比如LinearLayout)
须知:
- 让View支持wrap_content,因为直接维承View或者ViewGroup的控件,如不在onMeasure中对wrap_ content做特殊处理。那么使用无法达到预期效果
- 让你的View支持padding,直接继承View的控件默认padding属性无法起作用,所以要在draw方法中处理padding;直接维承ViewGroup的控件需要在onMeasure和onLayout中考虑padding和子元素的margin对其造成的影响,否则将导致padding和子元素的margin失效。
- 尽量不要在View中使用Handler
因为View内部提供了post系列的方法,可替代Handler,如果明确发送消息的话可以用handler - View中证有线程或动画,需要及时停止,不处理有可能会造成内存泄露,View不可见时也要停止线程和动画;包含View的Activity启动时, View的onAttachedToWindow方法会被调用;当包含此View的Activity取出或者当前View被remove时,View的onDetachedFromWindow方法会被调用
- 滑动嵌套, 处理好
实例
1.继承View 重写onDraw方法,需要处理wrap_content和padding
2.提供自定义属性:
2.1 values目录下创建自定义属性的XML
2.2 在View的构造方法中解析自定义属性的值并进行处理
2.3 布局文件中使用自定义属性
添加声明 xmlns:app="http://schemas.android.com/apk/res-auto"
使用属性 app:circle_color="@color/light_green"