版权声明:本文出自 双门M 的专栏,转载或使用请注明出处。
开发的时候,因为业务需求或者封装需要,我们会进行自定义控件。
说在前面,本篇涉及到一些东西
* onMeasure
* onLayout
* onDraw
* MeasureSpec (32位二进制数,头两位模式(Mode),后两位大小(Size))
* onFinishInflate
* ViewGroup的getViewAt 方法
* MeasureSpec的makeMeasureSpec方法,getSize,getMode,和AT_MOST、EXACTLY、UNSPECIFIFD三个常量。
* getMeasuredWidth和getMeasuredHeight
一、控件的简单分类
简单粗暴来说,控件可以分为2种,View和ViewGroup
View 单独的控件,里面不能存放控件,继承自View。比如Imageview Button TextView。
ViewGroup 能存放控件的容器,比如FrameLayout、RelativeLayout 和LinearLayout等。
二、自定义控件的分类
自定义控件,类型各有各的分法,本人的对其进行大概如下分类:
* 自制控件 : 该自定义控件继承自View或者ViewGroup,自己绘制。
* 组合控件 : 利用系统已提供的控件,组合成一个新的控件
* 拓展控件 : 继承自系统已提供的控件并且加上新的功能或者特性。
既然如此,我们就开始来吧,从自制控件说起。
(本文的最后会附上关于三类自定义控件的博文链接)
三、自制控件
如果用最简单接地气的语言来描述自制控件,那么就是
1、继承自View或者ViewGroup
2、利用 onMeasure(测量)、onLayout(摆放)和onDraw(绘制) 三大步骤弄来弄去把View搞出来。(三者不必同时用到)
3、在利用事件的触发机制等调一调onTouchEvent等方法监听一下,然后利用Scroller或者ViewDragHelper等做做动画之类,合适怎么整就怎么整。
4、写写接口,调调回调,该谁干活谁就干活。
说成一句话:继承自View或者ViewGroup然后测量摆放绘制,然后做做动画,最后写写接口给调用者调用。
小二,来一个最简单的自制控件吧
1、 需要先认识的简单知识
提示:如果觉得琐碎可以直接从三.2的实例看起,最后再回过头来看这个三.1的内容。效果可能更佳。
我们的ViewGroup继承自View,在View里面有这么几个重要方法
首先三个我们已经提到的
1.1、measure和onMeasure(),layout()和onLayout(),draw()和onDraw()
- measure() View的测量的入口,辗转调用真正工作的onMeasure()
- layout() View的摆放的入口,辗转调用真正工作的onLayout()
- draw() View的绘制的入口,辗转调用真正工作的onDraw()
其中,measure确定View的宽高,layout确定View的四个顶点的位置,而draw将View绘制在屏幕上。
measure()方法是final类型的方法,子类无法复写,而layout()和draw()子类就可以复写
- onMeasure() 回调该方法对控件进行测量
- onLayout() 回调该方法对控件位置进行摆放
- onDraw() 回调该方法对控件进行绘制
measure和onMeasure()
关于measure(),需要分两种情况讨论,
情况1,只是View,那么通过View的measure() 可以完成对View的测量,而measure() 会去调用 onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
情况2,是ViewGroup,measure()除了完成对容器自身的测量之外,接着还会遍历去调用子元素(子元素可以使View或者ViewGroup)的measure(),各个子元素再去递归调用自身子元素的measure。注意是递归,递归,递归,重要的说三遍。
在ViewGroup的measure()会去调用onMeasure(),然后我们需要在onMeasure()里面对容器里的孩子进行 孩子.measure() 对孩子进行测量。
layout()和onLayout()
对于layout
如果是View,View调用layout可以指定View的位置
如果是ViewGroup,那么onLayout() 里面进行 孩子.layout 可以精确摆放孩子的位置
draw()和onDraw()
对于draw
这个就 比较简单了,他是按照如下几个流程走的
1、绘制背景 backgroud.draw(canvas)
2、绘制本身( onDraw )
3、绘制孩子( dispatchDraw )
4、绘制装饰 ( onDrawScrollBars )
其实实际开发中我们常用的,关心的也就是onDraw方法。
其实说了这么多,大概就是需要明白的是:
一个View的从无到有需要经过measure(),layout(),draw()三个步骤,measure()会辗转调用measure(),layout()会辗转调用onLayout,draw会转转调用onDraw().
如果是View,那么就写写onDraw,onLayout,至于onMeasure一般不需要。
如果是ViewGroup,那么些在onMeasure里面测量自身后接着进行对孩子的测量,在onLayout里面进行孩子的位置摆放。
.
.
1.2、onFinishInflate方法
我们知道,我们在Xml写的布局文件最终会在通过Pull解析的方式转成代码的。
onFinishInflate的作用,就是在xml加载组件完成后调用的。这个方法一般在自制ViewGroup的时候调用。
/**
* Finalize inflating a view from XML. This is called as the last phase
* of inflation, after all child views have been added.
*
* <p>Even if the subclass overrides onFinishInflate, they should always be
* sure to call the super method, so that we get called.
*/
@CallSuper
protected void onFinishInflate() {
}
.
.
1.3、ViewGroup的 getChildAt(int position) 方法
Returns the view at the specified position in the group.
返回该组中指定位置的视图。
这个位置按照我们include的顺序排列,索引从0开始。
/**
* Returns the view at the specified position in the group.
*
* @param index the position at which to get the view from
* @return the view at the specified position or null if the position
* does not exist within the group
*/
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}
.
.
1.4 onMeasure和measure的参数
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
孩子.measure(32位的MeasureSpec宽,32位的MeasureSpec高);
}
但看起来他们是一个int值,这两个参数可以是宽高的意思,但是不完全是,这宽和高都是32由 32位的二进制码组成的,并不是随随便便穿进去一个int类型的值就完事了的。
既然不能随随便便,那我们应该传什么?——我们应该传一个32位的二进制数。
那么我们计算出或者拿到这个数?—— 使用View类里面的MeasureSpec这个静态内部类。
1.5、 View类里面的静态内部类 MeasureSpec
我们首先需要看一个类,MeasureSpec
从上图我们可以看到,MeasureSpec这个类给我们提供了4个方法和3个常量
makeMeasureSpec和32位的二进制int值
先看看几个方法
* makeMeasureSpec(int size,int mode)
返回值是int,我们就是用MeasureSpec的makeMeasureSpec方法制造32位的二进制数给measure(宽,高)这个方法。
其中,size占30位,mode占2位
这个方法的2个参数
size(大小)怎么指定?
mode怎么确定?
* 利用LayoutParams得到size *
我们的View有多大谁知道?——我们在写xml的布局文件的时候就知道了有多大了。
但是怎么获取文件的在xml布局时的大小呢?
利用View的getLayoutParams()方法可以得到一个LayoutParams类型的值,利用
layoutParams.width 和
layoutParams.height
就可以获得View在布局时的宽高。
* 怎么确定mode *
mode有3种模式,分别是EXACTLY:精确模式;AT_MOST最大值模式,UNSPECIFIED : 未指定的模式,
MeasureSpec.EXACTLY,精确模式
当我们的 layout_width 和 layout_height 设定为
填充父窗体 match_parent 或者
指定具体数值 比如 android:layout_width=”130dp” 时
就可以采用EXACTLY这种模式
.
MeasureSpec.AT_MOST,最大值模式
当我们的 layout_width 和 layout_height 设定为 warp_content 时,控件大小随着内容大小的变化而变化,就是采用AT_MOST这种模式。
.