自定义控件
近期写了一些,但是都没整理,抓点时间整理出来
1. View
基本流程:
> 1.构造函数:View的初始化
> 2.onMeasure:测量View大小
> 3.onSizeChanged:确定View大小(这一步并不关键)
> 4.onLayout:确定子View布局(自定义View包含子View时有用)
> 5.onDraw:实际绘制内容
> 6.监听接口:控制View或监听View某些状态。
1. 构造函数:
代码:
public CustomView(Context context) { this(context, null); } public CustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //1.初始化 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(getResources().getColor(R.color.colorPrimaryDark)); }
一个参数:在java中new时被使用。
两个参数:在XML中进行定义时被调用,第二个参数代表xml中定义的属性
三个参数:能够添加一个style。
使用this,以及默认属性将三个构造进行传递,最终被使用的都是三个参数的构造,而在进行初始化时也只需要在三个参数的构造函数中进行即可。
2.onMeasure:
- onMeasure基本流程:
ViewRoot -> performTraversals -> measure -> onMeasure
- View显示有两个前提,一是有自身的宽高属性,二是在整体中的位置,第一个宽高属性由onMeasure进行测量,而本身我们在xml中定义的宽高首先是由其对应的父控件决定,其次再由自己决定,所以需要进行这样一个onMeasure的流程。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
}
分析:
onMeasure中有两个参数,这两个参数其实不是表面上的意义,其本身由一个32位的值组成,第30和31位表示其测量模式,0到29表示其真是的宽高值,如下表:模式名称 模式数值 实际数值 对应的值 UNSPECIFIED 00 000000000000000000001111011000 自定义时使用 EXACTLY 01 000000000000000000001111011000 如50dp AT_MOST 10 000000000000000000001111011000 wrap_content 模式:
测量模式有三种,是在View类中的一个内部类进行定义的:View.MeasureSpec:- UNSPECIFIED:以00为默认值,父控件没有给子view任何的限制,可以设置为任意大小。
- EXACTLY:以01为默认值,父控件已经确切的指定了子view的大小。
- AT_MOST:以10位默认值,大小没有尺寸限制,上限为父View的大小,即wrap_content。
measure:
- measure是每个view中一定存在一个必调的方法,measure(widthMeasureSpec,heightMeasureSpec) ,它调用onMeasure(int,int)
- 是用于确定视图的高度和宽度的规格和大小
- 未复写该方法时调用,getDefaultSize方法进行测量
从其内部实现看,视图大小的控制是由父视图,布局文件,以及视图本身共同完成的,父视图会提供给 子视图参考的大小,而开发人员可以在XML文件中指定视图大小,然后视图本身会对最终的大小进行排版。
3. onSizeChanged:
这个函数在视图大小发生改变时调用。
在测量完View并使用setMeasuredDimension函数之后View的大小基本上已经确定了,但View的大小不仅由View本身控制,还受父控件的影响,所以我们在确定View大小的时候最好使用系统提供的onSizeChanged回调函数。
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); }
- 其四个参数分别为 宽度,高度,上一次宽度,上一次高度。
从上图中可以看出,onSizeChanged方法比onMeasure进行的少,而当我们在需要获取宽高等操作时,可以不再放在onMeasure中进行,使用onSizeChanged也能够提高一些效率。
插入一条:getWidth和getMeasureWidth基本相同,前者在layout之后获取,后者在measure后获取到, 后者的值通过setMeasureDimension设置,前者由本视图通过右边坐标减左边坐标计算的。
4. onLayout:
- onLayout基本流程:
ViewRoot -> performTraversals -> layout-> onLayout
- layout:
- 参数:int类型,分别为相对当前父视图的左,上,右,下的值。
- 在其内部的过程:
- 判断视图大小是否发生过变化,以确定有没有必要对当前视图进行重绘,同时也将传递过来的四个参数分别复制给成员变量型的左上右下。
- 然后进入onLayout进行定位,且默认该方法内部实现为空。因为定位其位置的操作是又布局完成,是由其父布局决定他的显示位置。而在ViewGroup中的onLayout是一个抽象方法,即表明在自定义或是xml中都需要我们手动对其覆写和定义的。
5. onDraw:
- onDraw的基本流程:
ViewRoot -> new Canvas -> draw -> 四个关键步骤
绘制的四个关键步骤:
其实有6步,但另外两步使用的很少,忽略了。
- 画背景:
如果需要的话:得到mBGDrawable对象,然后根据layout过程确定的视图位置来设置背 景的绘制区域,然后调用Drawable的draw方法来完成背景的绘制工作,mBGDrawable由布局文件中 的android:background来的,也可以通过setBackgroundColor和setBackgroundResource 来赋值获取 绘制视图内容:
即onDraw(canvas)。内部实现为空,由子类实现,View内部不会进行内容的绘制,而非大部分我们是由布局文件中的定义进行确定内容,之后由View进 行绘制,如TextView和ImageView,内部通过Canvas进行绘制。自定义中需要我们进行重写。对当前视图的所有子视图进行绘制,如果没有子视图,就不需要进行绘制。dispatchDraw内部实现依旧为空。
- 对视图的滚动条进行绘制。
onDraw部分的内容很多,这里简略讲,会单开一章介绍的。
- 画背景:
未完待续。。。