1 View生命周期
2.自定义View分为两类
-
继承与View
在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View,SurfaceView或其他的View。
自定义View主要是实现 onMeasure + onDraw
-
继承与ViewGroup(现有布局)
利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout。
自定义ViewGroup主要是实现:onMeasure + onLayout
3 自定义View绘制流程
-
创建View
-
继承View/ViewGroup等,实现构造函数。
需要实现包含Context和AttributeSet参数的构造,该构造允许我们在xml文件中创建和编辑自定义控件实例。
-
attrs.xml文件中声明自定义属性,xml文件中使用自定义属性。
-
在构造函数中通过AttributeSet获取TypedArray,根据TypedArray获取自定义属性,其中TypedArray对象为共享资源,获取完属性后需要回收。
-
添加设置自定义属性的Java接口
在xml中指定的自定义属性只有在view被初始化的时候能够获取到,动态设置自定义属性后需要调用invalidate(),重新绘制View。
-
-
测量View:onMeasure()
MeasureSpec
onMeasure有两个参数widthMeasureSpec, heightMeasureSpec,MeasureSpec包含父容器传给View的测量模式和大小。
MeasureSpec一个32位的int值,高2位代表SpecMode是指测量模式,低30位代表SpecSize是指在某种测量模式下的View大小。
SpecMode有三类:
-
UNSPECIFIED
父容器对View没有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态,ScrollView/listview测量子View时用的就是这个。
-
EXACTLY
确切的大小,父容器已检测出View所需大小,View的最终大小就是SpaecSize,对应LayoutParams中的matche_parent和具体的数值(dp)两种模式。
-
AT_MOST
指定View的最大值,View的大小不能大于这个值,对应LayoutParams中的wrap_content。
两类自定义View的测量流程:
-
自定义ViewGroup
遍历所有子View,通过getChildMeasureSpec获取子View的MeasureSpec,调用子View的measure,并计算自身的大小,调用setMeasureDimension,保存控件的大小,否则会因为自身大小为0,不显示。
getChildMeasureSpec算法:
childLayoutParams/
parentSpecModeEXACTLY AT_MOST UNSPECIFIED dp/px EXACTLY
childSizeEXACTLY
childSizeEXACTLY
childSizematch_parent EXACTLY
parentSizeAT_MOST
parentSizeUNSPECIFIED
0wrap_content AT_MOST
parentSizeAT_MOST
parentSizeUNSPECIFIED
0 -
自定义View
直接计算自身的大小,调用setMeasureDimension,保存控件的大小,否则会因为自身大小为0,不显示。
View大小算法:
-
EXACTLY:父容器传递的大小measureSize
-
UNSPECIFIED:希望View的大小desireSize
-
AT_MOST:min(desireSize,specSize)
-
-
-
布局View:onLayout()(自定义ViewGroup需要实现)
根据onMeasure中记录的View大小,布局子View。
-
绘制View:onDraw()(自定义Viewp需要实现)
自定义控件被创建,并且测量、布局之后,接下来就需要实现onDraw()来绘制View了。
onDraw方法包含了比较重要的两个参数::
- Canvas(画布):决定要去画什么
- Paint(画笔):决定怎么画 比如,Canvas提供了画线方法,Paint就来决定线的颜色。Canvas提供了画矩形,Paint又可以决定让矩形是空心还是实心。
在onDraw方法中开始绘制之前,你应该让画笔Paint对象的信息初始化完毕。这是因为View的重新绘制是比较频繁的,这就可能多次调用onDraw,所以初始化的代码不应该放在onDraw方法里。
-
与用户进行交互
自定义控件需支持点击、拖拽等操作,可在onTouchEvent中处理触摸事件,并对外提供接口。
-
优化已定义的View
自定义控件需运行流畅,为了避免你的控件看得来迟缓,确保动画始终保持每秒60帧。
需注意以下几点:
- 避免不必要的代码
- 在onDraw()方法中不应该有会导致垃圾回收的代码。
- 尽可能少让onDraw()方法调用,大多数onDraw()方法调用都是手动调用了invalidate()的结果,所以如果不是必须,不要调用invalidate()方法,比如动态设置自定义属性,需要调用invalidate才能生效,所以应该设置完所有需要设置的属性后,在调用invalidate()。
4 补充知识点
-
为什么要measure
onMeasure方法就是测量View/子View自身的大小并且存储测量的大小的。
-
getMeasureWidth与getWidth的区别
-
getMeasureWidth:在measure()过程结束后就可以获取到对应的值;是通过setMeasuredDimension()方法来进行设置的。
-
getWidth:在layout()过程结束后才能获取到;是通过视图右边的坐标减去左边的坐标计算出来的。
-