一、介绍
为什么要自定义view?明明Android系统内置的控件和布局已经很多了。但是,有了产品经理就不一样了,那些控件根本不够用呀,有些效果利用原生的控件无法实现,所以就需要自定义View.
二、自定义View
自定义View我们大部分时候只需重写两个函数:onMeasure()、onDraw()。onMeasure负责对当前View的尺寸进行测量,onDraw负责把当前这个View绘制出来。当然了,你还得写至少写2个构造函数:
public CView(Context context) {
super(context);
}
public CView(Context context, AttributeSet attrs) {
super(context, attrs);
}
1.onMeasure
首先我们要测量View的宽高尺寸。可以通过重写父类的onMeasure方法,在onMeasure中完成自定义的尺寸。
下面是onMeasure方法的原型
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
解释一下:widthMeasureSpec和heightMeasureSpec包含了view的宽高信息,我们可以通过MeasureSpec中的方法获取view的宽高信息
int width = MeasureSpec.getSize(widthMeasureSpec); //获取自定义view的宽
int height = MeasureSpec.getSize(heightMeasureSpec);//获取自定义view的高
int widthMode = MeasureSpec.getMode(widthMeasureSpec);//获取自定义view的宽的测量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);//获取自定义view的高的测量模式
接下来我们看一下测量模式
测量模式 | 表示意思 |
---|---|
UNSPECIFIED | 父容器没有对当前View有任何限制,当前View可以任意取尺寸 |
EXACTLY | 当前的尺寸就是当前View应该取的尺寸 |
AT_MOST | 当前尺寸是当前View能取的最大尺寸 |
而上面的测量模式跟我们的布局时的wrap_content、match_parent以及写成固定的尺寸有什么对应关系呢?
match_parent--->EXACTLY。怎么理解呢?match_parent就是要利用父View给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。
wrap_content--->AT_MOST。怎么理解:就是我们想要将大小设置为包裹我们的view内容,那么尺寸大小就是父View给我们作为参考的尺寸,只要不超过这个尺寸就可以啦,具体尺寸就根据我们的需求去设定。
固定尺寸(如100dp)--->EXACTLY。用户自己指定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主啦。
2.onDraw
首先我们先深入了解两个类Paint和Canvas。
2.1.paint
/**
* Paint类介绍
*
* Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色,
* 样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法,
* 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。
*
* 1.图形绘制
* setARGB(int a,int r,int g,int b);
* 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。
*
* setAlpha(int a);
* 设置绘制图形的透明度。
*
* setColor(int color);
* 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。
*
* setAntiAlias(boolean aa);
* 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
*
* setDither(boolean dither);
* 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
*
* setFilterBitmap(boolean filter);
* 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示
* 速度,本设置项依赖于dither和xfermode的设置
*
* setMaskFilter(MaskFilter maskfilter);
* 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 *
* setColorFilter(ColorFilter colorfilter);
* 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果
*
* setPathEffect(PathEffect effect);
* 设置绘制路径的效果,如点画线等
*
* setShader(Shader shader);
* 设置图像效果,使用Shader可以绘制出各种渐变效果
*
* setShadowLayer(float radius ,float dx,float dy,int color);
* 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色
*
* setStyle(Paint.Style style);
* 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE Style.FILL: 实心 STROKE:空心 FILL_OR_STROKE:同时实心与空心
*
* setStrokeCap(Paint.Cap cap);
* 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式
* Cap.ROUND,或方形样式Cap.SQUARE
*
* setSrokeJoin(Paint.Join join);
* 设置绘制时各图形的结合方式,如平滑效果等
*
* setStrokeWidth(float width);
* 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度
*
* setXfermode(Xfermode xfermode);
* 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果
*
* 2.文本绘制
* setFakeBoldText(boolean fakeBoldText);
* 模拟实现粗体文字,设置在小字体上效果会非常差
*
* setSubpixelText(boolean subpixelText);
* 设置该项为true,将有助于文本在LCD屏幕上的显示效果
*
* setTextAlign(Paint.Align align);
* 设置绘制文字的对齐方向
*
* setTextScaleX(float scaleX);
* 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果
*
* setTextSize(float textSize);
* 设置绘制文字的字号大小
*
* setTextSkewX(float skewX);
* 设置斜体文字,skewX为倾斜弧度
*
* setTypeface(Typeface typeface);
* 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
*
* setUnderlineText(boolean underlineText);
* 设置带有下划线的文字效果
*
* setStrikeThruText(boolean strikeThruText);
* 设置带有删除线的效果
*
*/
画笔这里, setStyle(Paint.Style style)
这里通过http://www.cnblogs.com/aibuli/p/efef9d774df97c553a8a0c0c3495ba35.html?utm_source=tuicool&utm_medium=referral学习了paint的用法
2.2.Canvas
Canvas类简单理解就是表示一块画布,可以在上面画我们想画的东西
Canvas中的方法很多,Canvas可以绘制的对象有:
弧线(arcs) canvas.
填充颜色(argb和color)
Bitmap
圆(circle和oval)
点(point)
线(line)
矩形(Rect)
图片(Picture)
圆角矩形 (RoundRect)
文本(text)
顶点(Vertices)
路径(path)
canvas.save():把当前的绘制的图像保存起来,让后续的操作相当于是在一个新的图层上的操作。
canvas.restore(); 把当前画布返回(调整)到上一个save()状态之前
canvas.translate(dx, dy); //把当前画布的原点移到(dx,dy),后面的操作都以(dx,dy)作为参照点,默认原点为(0,0)
canvas.scale(x,y);扩大。x为水平方向的放大倍数,y为竖直方向的放大倍数
canvas.rotate(angel):旋转.angle指旋转的角度,顺时针旋转。
canvas.transform():切变。所谓切变,其实就是把图像的顶部或底部推到一边。
canvas.saveLayer(bounds, paint, saveFlags);
学会了这两个方法就可以通过onDrow(Canvas canvas)绘制我们想要的图形了
protected void onDraw(Canvas canvas) { //重载onDraw方法
// TODO Auto-generated method stub
super.onDraw(canvas);
paint.setColor(Color.RED); //设置画笔颜色
paint.setStyle(Style.FILL); //设置画笔为空心 如果将这里改为Style.STROKE 这个图中的实线圆柱体就变成了空心的圆柱体
canvas.drawColor(Color.WHITE);
canvas.drawLine(50, 50, 450, 50, paint); //绘制直线
canvas.drawRect(100, 100, 200, 600, paint); //绘制矩形
canvas.drawRect(300, 100, 400, 600, paint); //绘制矩形
}
三、自定义ViewGroup
我们都知道ViewGroup是个View容器,它装纳child View并且负责把child View放入指定的位置
1.首先,我们得知道各个子View的大小,只有先知道子View的大小,我们才知道当前的ViewGroup该设置为多大去容纳它们。
2.根据子View的大小,以及我们的ViewGroup要实现的功能,决定出ViewGroup的大小
3.ViewGroup和子View的大小算出来了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得根据你定制的需求去摆放了,比如,你想让子View按照垂直顺序一个挨着一个放,或者是按照先后顺序一个叠一个去放,这是你自己决定的。
4.已经知道怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间"分割"成大大小小的空间,每个空间对应一个子View,我们接下来就是把子View对号入座了,把它们放进它们该放的地方去。
首先重写onMeasure,实现测量子View大小以及设定ViewGroup的大小;
上面的onMeasure将子View测量好了,以及把自己的尺寸也设置好了,接下来我们去摆放子View吧~
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//此方法中写控件的摆放逻辑
int count = getChildCount();
//记录当前的高度位置
int curHeight = t;
//将子View逐个摆放
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
//摆放子View,参数分别是子View矩形区域的左、上、右、下边
child.layout(l, curHeight, l + width, curHeight + height);
curHeight += height;
}
}
然后就可以去布局文件中使用了
四、自定义布局属性
如果有些属性我们希望由用户指定,只有当用户不指定的时候才用我们硬编码的值,比如上面的默认尺寸,我们想要由用户自己在布局文件里面指定该怎么做呢?那当然是通我们自定属性,让用户用我们定义的属性啦~
首先我们需要在res/values/styles.xml文件(如果没有请自己新建)里面声明一个我们自定义的属性:
接下来就是在布局文件用上我们的自定义的属性啦~
注意:需要在根标签(LinearLayout)里面设定命名空间,命名空间名称可以随便取,比如hc,命名空间后面取得值是固定的:"http://schemas.android.com/apk/res-auto"
最后就是在我们的自定义的View里面把我们自定义的属性的值取出来,在构造函数中,还记得有个AttributeSet属性吗?就是靠它帮我们把布局里面的属性取出来:
private int defalutSize;
public CView(Context context, AttributeSet attrs) {
super(context, attrs);
//第二个参数就是我们在styles.xml文件中的标签
//即属性集合的标签,在R文件中名称为R.styleable+name
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称
//第二个参数为,如果没有设置这个属性,则设置的默认的值
defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
//最后记得将TypedArray对象回收
a.recycle();
}
自定义view暂时记录,还需深入学习。。。
写在最后: 本系列的文章旨在学习过程中的总结,如果对你也有帮助,荣幸之至。