自定义View
1、什么是自定义View?
自定义View可分为三类:
a、把系统内置的控件组合起来生成一个新的控件
b、继承系统现有的控件,然后加入新的功能
c、自己绘制控件,继承系统的View类,通过View中的回调方法实现绘制
2、为什么使用自定义View?
自定义View可以实现系统View满足不了的需求,根据我们开发中不同的需求去实习我们自己的View.通过自定义View我们还可以实现一些炫酷的效果提升产品体验.
3、自定义View的三个方法:
onMeasure()、onLayout()、onDraw().
4、View要是显示出来要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout、draw
测量:onMeasure() 决定View的大小
布局:onLayout() 决定View在ViewGroup中的位置
绘制:onDraw() 决定绘制这个View
***自定义控件又分为自定义View和自定义ViewGroup,自定义View只需要重写onMeasure()和onDraw(),而自定义ViewGroup需要重新onMeasure()和onLayout().
5、Measure
作用:测量View的宽/高
注:
a、在某些情况下,需要多次测量(measure)才能确定View最终的宽/高.
b、在这种情况下measure过程后得到的宽/高可能是不准确的.
c、建议在layout过程中onLayout()去获取最终的宽/高.
measure传递尺寸(宽/高测量值)的两个类:
ViewGroup.LayoutParams (View自身的布局参数)
MeasureSpecs类 (父视图对子视图的测量要求)
(1)、ViewGroup.LayoutParams
![](https://img.haomeiwen.com/i17960202/4b4e588b884f09e8.png)
作用:指定视图的(height)和宽度(width)等布局参数.可通过以下参数指定:
ViewGroup的子类有其对应的ViewGroup.LayoutParams子类:
a、ViewGroup的子类包括RelativeLayout、LinearLayout等.
b、RelativeLayout的ViewGroup.LayoutParams的子类是RelativeLayoutParams.
构造函数:
// View的构造函数有四种重载
public DIY_View(Context context){
super(context);
}
public DIY_View(Context context,AttributeSet attrs){
super(context, attrs);
}
public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ){
super(context, attrs,defStyleAttr);
// 第三个参数:默认Style
// 默认Style:指在当前Application或Activity所用的Theme中的默认Styl
(2)、MeasureSpec
定义:测量规格(测量View的依据)(每个MeasureSpec代表了一组宽度和高度的测量规格)
作用:决定了一个View的大小(宽/高)(即宽测量值(widthMeasureSpec)和高测量值(heightMeasureSpec)决定了View的大小)
分类:widthMeasureSpec(宽测量规格)、heightMeasureSpec(高测量规格)
组成:MeasureSpec = mode + size
(测量规格,32位的int值) (测量模式,高2位即31、32位+具体测量大小,底30位)
Mode模式共分为三类:
![](https://img.haomeiwen.com/i17960202/1e768684c79e6bf1.png)
总结:(以子View为标准,横向观察)
(1)、当子View采用具体数值(dp\px)时
无论父容器的测量模式是什么,子View的测量模式都是EXACTLY且大小等于设置的具体数值.
(2)、当子View采用match_parent时
子View的测量模式与父容器的测量模式一致.若测量模式为EXACTLY,则子View的大小为父容器的剩余空间;若测量模式为AT_MOST,则子View的大小不超过父容器的剩余空间.
(3)、当子View采用wrap_parent时
无论父容器的测量模式是什么,子View的测量模式都是AT_MOST且大小不超过父容器的剩余空间.
(3)、Measure过程:(如果需要重新Measure应该调用RequestLayout()方法)
measure过程根据View的类型分两种:
a、View类型 = 单一View时:只测量自身一个View.
b、View类型 = ViewGroup时:对ViewGroup视图中所有的子View都进行测量(即遍历去调用所有子元素的measure方法,然后各子元素再递归去执行这个流程)
单一View的measure过程:
![](https://img.haomeiwen.com/i17960202/adef63e79bdc50ec.png)
应用场景:在没有现成的View,需要自己实现的时候就使用自定义View,一般继承自View,surfaceView或其他的View,不包含子View.
过程:
对于每个方法的总结:
![](https://img.haomeiwen.com/i17960202/f7cea8b75b52dbd3.png)
ViewGroup的measure过程:
应用场景:自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout(含有子View).
原理:通过遍历所有的子View进行子View的测量,然后将所有子View的尺寸进行合并,最终得到ViewGroup父视图的测量值.
过程:
![](https://img.haomeiwen.com/i17960202/2fcf45b94906005b.png)
对于每个方法的总结:
![](https://img.haomeiwen.com/i17960202/b2ac80b72c815a69.png)
(4)、onMeasure()方法:
作用:测量当前View的在屏幕上占用的尺寸.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//此方法是设置View的测量值
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}```
其中```setMeasuredDimension ```方法是设置View测量的值获取View值的方法是```getDefaultSize ``` ,我们再看一下它的源码:
```java
public static int getDefaultSize(int size, int measureSpec) {
int result = 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;
}
从getDefaultSize方法中可用看出View的宽高都由specSize决定,可得:在View中使用wrap_content就相当于是使用match_parent,所以直接继承系统View的类的控件需要重写onMeasure.
(5)、onDraw()方法:
作用:把已经测量好的View画在屏幕上,开发者根据自己的需要绘制不同的功能.
onDraw方法是通过view的draw方法调用,一般有以下几步:
a、绘制背景 background.draw(canvas)
b、绘制自己调用onDraw方法
c、绘制children调用dispatchDrae方法
d、绘制装饰 onDrawScrollBars方法
public void draw(Canvas canvas) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
// 第一步 绘制背景
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
//第二步 绘制自己
if (!dirtyOpaque) onDraw(canvas);
//第三步 绘制childern
dispatchDraw(canvas);
//第四步 绘制装饰
onDrawScrollBars(canvas);
//以下省略...
return;
}
}
网友评论