1.概述:
Android自定义View/ViewGroup步骤大致如下
1:自定义属性
2:选择和设置构造方法
3:重写omMeasure()
4:重写onDraw()
5:重写onLayout()
6:重写其他事件的方法,比如:滑动监听
2.自定义属性的方法
自定义属性主要有定义,使用,获取这三个步骤。
2.1定义自定义属性:
通常将自定义属性放入attr.xml文件里面,这个文件需要自己创建
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="rightPadding" format="dimension" />
<declare-styleable name="CustomMenu">
<attr name="rightPadding" />
</declare-styleable>
</resources>
- declare-stylable标签只是为了给自定义属性分类。一个项目中可能又多个自定义控件,但只能又一个attr.xml文件,因此我们需要对不同自定义控件中的自定义属性进行分类,这也是为什么declare-stylable标签中的name属性往往定义成自定义控件的名称;
常用的format类型:
1:string:字符串类型
2:integer:整数类型
3:float:浮点类型
4:dimension:尺寸,后必须跟dp,dip,px,sp等单位
5:Boolean:布尔值
6:reference:引用类型,传入的是某一资源ID,以“@”符号开头
7:color:颜色,以#开头
8:fraction:百分比,以%结尾
9:enum:枚举类型
- format中可以写多种类型,中间使用“|”符号分割开,表示这几种类型都可以传入这个属性;
- enum类型的定义示例如下代码所示:
<resources>
<attr name="orientation">
<enum name="horizontal" value="0" />
<enum name="vertical" value="1" />
</attr>
<declare-styleable name="CustomView">
<attr name="orientation" />
</declare-styleable>
</resources>
获取时通过getInt()方法获取到value并判断。
2.2使用自定义属性
在xml布局文件里面使用;自定义属性的时候,我们需要先自定义namespace。Android中默认的namespace是android
xmlns:android="http://schemas.android.com/apk/res/android"
因此我们在使用属性的时候都是用“android:***”。因此我们需要添加一个命名空间。自定义属性的命名空间如下:app的名字随自己定
xmlns:app="http://schemas.android.com/apk/res-auto"
2.3获取自定义属性
在代码里面我们可以通过TypedArray获取自定义属性的值。
public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomMenu, defStyleAttr, 0);
int indexCount = a.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.CustomMenu_rightPadding:
mMenuRightPadding = a.getDimensionPixelSize(attr, 0);
break;
}
}
a.recycle();
}
注意:
- 获取自定义属性的代码通常是在三个参数的构造方法中编写的(具体为什么是三个参数的构造方法,下面的章节中会有解释);
- 在获取TypedArray对象时就为其绑定了该自定义View的自定义属性集(CustomMenu),通过getIndexCount()方法获取到自定义属性的数量,通过getIndex()方法获取到某一个属性,最后通过switch语句判断属性并进行相应的操作;
- 在TypedArray使用结束后,需要调用recycle()方法回收它。
3、构造方法
当我们定义一个新的类继承了View或ViewGroup时,系统都会提示我们重写它的构造方法。View / ViewGroup中又四个构造方法可以重写,它们分别有一、二、三、四个参数。四个参数的构造方法我们通常用不到,因此这个章节中我们主要介绍一个参数、两个参数和三个参数的构造方法(这里以CustomMenu控件为例)。
3.1、一个参数的构造方法
构造方法的代码: public CustomMenu(Context context) { …… }
这个构造方法只有一个参数Context上下文。当我们在JAVA代码中直接通过new关键在创建这个控件时,就会调用这个方法。
3.2、两个参数的构造方法
构造方法的代码: public CustomMenu(Context context, AttributeSet attrs) { …… }
这个构造方法有两个参数:Context上下文和AttributeSet属性集。当我们需要在自定义控件中获取属性时,就默认调用这个构造方法。AttributeSet对象就是这个控件中定义的所有属性。
我们可以通过AttributeSet对象的getAttributeCount()方法获取属性的个数,通过getAttributeName()方法获取到某条属性的名称,通过getAttributeValue()方法获取到某条属性的值。
注意:不管有没有使用自定义属性,都会默认调用这个构造方法,“使用了自定义属性就会默认调用三个参数的构造方法”的说法是错误的。
3.3、三个参数的构造方法
构造方法的代码: public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { …… }
这个构造方法中有三个参数:Context上下文、AttributeSet属性集和defStyleAttr自定义属性的引用。这个构造方法不会默认调用,必须要手动调用,这个构造方法和两个参数的构造方法的唯一区别就是这个构造方法给我们默认传入了一个默认属性集。
defStyleAttr指向的是自定义属性的<declare-styleable>标签中定义的自定义属性集,我们在创建TypedArray对象时需要用到defStyleAttr。
3.4、三个构造方法的整合
一般情况下,我们会将这三个构造方法串联起来,即层层调用,让最终的业务处理都集中在三个参数的构造方法。我们让一参的构造方法引用两参的构造方法,两参的构造方法引用三参的构造方法。示例代码如下:
public CustomMenu(Context context) {
this(context, null);
}
public CustomMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 业务代码
}
这样一来,就可以保证无论使用什么方式创建这个控件,最终都会到三个参数的构造方法中处理,减少了重复代码。
4.onMwesure()
通过onMeasure()方法提供的widthMeasureSpec和heightMeasureSpec来分别获取控件的宽度和高度的测量模式和测量值(widthMeasureSpec = 测量模式+测量值)。
因此我们通过MeasureSpec.getMode(xMeasureSpec)和MeasureSpec.getSize(xMeasureSpec)分别获取测量模式和测量值
比如:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMySize(100,widthMeasureSpec);
int height = getMySize(100,heightMeasureSpec);
if (width < height) {
height = width;
} else {
width = height;
}
//设置view的高宽度
setMeasuredDimension(width, height);
}
//设置view的宽高度
private int getMySize(int defaultSize,int meansureSpec){
int mySize = defaultSize;
int mode = MeasureSpec.getMode(meansureSpec);//获取测量模式
int size = MeasureSpec.getSize(meansureSpec);
switch (mode){
case MeasureSpec.UNSPECIFIED:{//如果没有指定大小,就设置为默认大小
mySize = defaultSize;
break;
}
case MeasureSpec.AT_MOST:{//如果测量模式是最大取值为size
//将大小取最大值,
mySize = size;
break;
}
case MeasureSpec.EXACTLY:{//如果是固定大小,那就不用去改变
mySize = size;
break;
}
}
return mySize;
}
}
测量模式分为以下三种情况:
1) EXACTLY:当宽高值设置为具体值时使用,如100DIP、match_parent等,此时取出的size是精确的尺寸;
2) AT_MOST:当宽高值设置为wrap_content时使用,此时取出的size是控件最大可获得的空间;
3) UNSPECIFIED:当没有指定宽高值时使用(很少见)。
onMeasure()方法中常用的方法:
1) getChildCount():获取子View的数量;
2) getChildAt(i):获取第i个子控件;
3) subView.getLayoutParams().width/height:设置或获取子控件的宽或高;
4) measureChild(child, widthMeasureSpec, heightMeasureSpec):测量子View的宽高;
5) child.getMeasuredHeight/width():执行完measureChild()方法后就可以通过这种方式获取子View的宽高值;
6) getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;
7) setMeasuredDimension(width, height):重新设置控件的宽高。如果写了这句代码,就需要删除“super. onMeasure(widthMeasureSpec, heightMeasureSpec);”这行代码。
5.onDeaw()
6.Paint类:只要是设置画笔的属性,见如下:
1.图形绘制:
setStyle(Paint.Style s):设置画笔的样式:FILL实心;STROKE空心;FILL_OR_STROKE同时实心与空心
setArgb(int a,int r,int g,int b)设置绘制的颜色,a代表透明度,rgb代表颜色
setAlpha(int a)设置透明度
setColor(int color)设置绘制的颜色
setAntiAlias(boolean a)设置时候使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会变慢
setDither(boolean b)设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清楚
setFileterBitmap(Boolean b)设置是否设置在动画中滤掉bitmap的优化,更加加快显示速度
setMaskFilter(ColorFilter cf)设置颜色过滤器,可以在绘制颜色时实现不同的颜色变化效果
setPathEffect(PathEffect pe)设置绘制的路径效果
setShader(Sharder s)设置Shader绘制各种渐变效果
setShadowLayer(float r,int x,int y,int c)在图形下面设置阴影层,r为阴影角度,x和y为阴影在x轴和y轴上的距离,c为阴影的颜色
setStrokeCap(Paint.Cap c):当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式;
setStrokeJoin(Paint.Join j):设置绘制时各图形的结合方式;
setStrokeWidth(float w):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;
setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XXX)):设置图形重叠时的处理方式;
1.PorterDuff.Mode.CLEAR:所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC:显示上层绘制图片
3.PorterDuff.Mode.DST:显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER:正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER:上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN:取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN:取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT:上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT:取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP:取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP:取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR:异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN:取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN:取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY:取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN:取两图层全部区域,交集部分变为透明色
2.文本绘制
setTextAlign(Path.Align a):设置绘制的文本的对齐方式;
setTextScaleX(float s):设置文本在X轴的缩放比例,可以实现文字的拉伸效果;
setTextSize(float s):设置字号;
setTextSkewX(float s):设置斜体文字,s是文字倾斜度;
setTypeFace(TypeFace tf):设置字体风格,包括粗体、斜体等;
setUnderlineText(boolean b):设置绘制的文本是否带有下划线效果;
setStrikeThruText(boolean b):设置绘制的文本是否带有删除线效果;
setFakeBoldText(boolean b):模拟实现粗体文字,如果设置在小字体上效果会非常差;
setSubpixelText(boolean b):如果设置为true则有助于文本在LCD屏幕上显示效果;
3.其他方法
getTextBounds(String t, int s, int e, Rect b):将页面中t文本从s下标开始到e下标结束的所有字符所占的区域宽高封装到b这个矩形中;
clearShadowLayer():清除阴影层;
measureText(String t, int s, int e):返回t文本中从s下标开始到e下标结束的所有字符所占的宽度;
reset():重置画笔为默认值。
7.Cancas类
Canvas即画布,其上可以使用Paint画笔对象绘制很多东西。
1) drawArc():绘制圆弧;
2) drawBitmap():绘制Bitmap图像;
3) drawCircle():绘制圆圈;
4) drawLine():绘制线条;
5) drawOval():绘制椭圆;
6) drawPath():绘制Path路径;
7) drawPicture():绘制Picture图片;
8) drawRect():绘制矩形;
9) drawRoundRect():绘制圆角矩形;
10) drawText():绘制文本;
11) drawVertices():绘制顶点。
Canvas对象的其他方法:
1) canvas.save():把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制;
2) canvas.restore():把当前画布调整到上一个save()之前的状态;
3) canvas.translate(dx, dy):把当前画布的原点移到(dx, dy)点,后续操作都以(dx, dy)点作为参照;
4) canvas.scale(x, y):将当前画布在水平方向上缩放x倍,竖直方向上缩放y倍;
5) canvas.rotate(angle):将当前画布顺时针旋转angle度。
8.其他方法
8.1、onTouchEvent()
onTouchEvent()方法用来监测用户手指操作。我们通过方法中MotionEvent参数对象的getAction()方法来实时获取用户的手势,有UP、DOWN和MOVE三个枚举值,分别表示用于手指抬起、按下和滑动的动作。每当用户有操作时,就会回掉onTouchEvent()方法。
8.2、onScrollChanged()
如果我们的自定义View / ViewGroup是继承自ScrollView / HorizontalScrollView等可以滚动的控件,就可以通过重写onScrollChanged()方法来监听控件的滚动事件。
这个方法中有四个参数:l和t分别表示当前滑动到的点在水平和竖直方向上的坐标;oldl和oldt分别表示上次滑动到的点在水平和竖直方向上的坐标。我们可以通过这四个值对滑动进行处理,如添加属性动画等。
8.3、invalidate()
invalidate()方法的作用是请求View树进行重绘,即draw()方法,如果视图的大小发生了变化,还会调用layout()方法。
一般会引起invalidate()操作的函数如下:
1) 直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身; 2) 调用setSelection()方法,请求重新draw(),但只会绘制调用者本身; 3) 调用setVisibility()方法,会间接调用invalidate()方法,继而绘制该View; 4) 调用setEnabled()方法,请求重新draw(),但不会重新绘制任何视图,包括调用者本身。
8.4、postInvalidate()
功能与invalidate()方法相同,只是postInvalidate()方法是异步请求重绘视图。
8.5、requestLayout()
requestLayout()方法只是对View树进行重新布局layout过程(包括measure()过程和layout()过程),不会调用draw()过程,即不会重新绘制任何视图,包括该调用者本身。
8.6、requestFocus()
请求View树的draw()过程,但只会绘制需要重绘的视图,即哪个View或ViewGroup调用了这个方法,就重绘哪个视图。
参考:网址:https://www.cnblogs.com/itgungnir/p/6217447.html