1、自定义view
自定义view的一般步奏是:
1、 继承view、
2、 重写构造方法、
3、 重写onMeasure方法、
4、 重写onDraw方法。
5、 也可以使用自定义属性:
使用的优先级
直接在XML中定义>style定义>由defStyleAttr和defStyleRes指定的默认值>直接在Theme中指定的值
1、通过
2、在xml中为相应的属性指定属性值
- 在layout布局文件中
- 设置style并在style中设置属性
- 在theme中指定在当前Application或Activity中属性的默认值
3、在运行时(一般为构造函数)获取属性值
我们要获取的属性值都是通过这个函数返回的TypedArray获得的
TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes){}
四个参数:
- set:属性值的集合
- attrs:属性资源的ID
- defStyleAttr:当前Theme中的一个attribute,是一个指向style的一个引用,当在layout xml中和style中都没有为View指定属性时,会从Theme中这个attribute指向的Style中查找相应的属性值。如果这个参数传入0表示不在Theme中搜索默认值
- defStyleRes:指向一个Style的资源,但是仅在defStyleAttr为0或defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用。也是指定自定义view的默认属性。
4、将获取到的属性值应用到View
1、构造函数
一个参数 (Context context):
直接new一个view的时候调用两个参数 (Context context, AttributeSet attrs):
在layout布局文件中使用的时候会调用,关于它的所有属性(包括自定义属性,还有在这个layout中通过style为view添加的属性)都会包含在attrs中传递进来。三个参数 (Context context, AttributeSet attrs, int defStyleAttr):
不会自动调用,一般在两个参数的构造函数中手动调用,重写这个构造函数的意义是,为自定义view提供一些默认的属性。
其中defStyleAttr,是默认的属性,它的取值是,是在Theme中style的值。四个参数 (Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes):
minSdkVersion>=21才可以使用
一般来说,你只需实现前两个
3、测量view的大小(onMeasure)
当宽高设置为match_parent、wrap_content时,系统帮我们测量的宽高都是match_parent的长度,所以在设置了wrap_content时,需要手动进行测量(重写onMeasure())。
在重新onMeasure方法时,需要注意 MeasureSpec 。 MeasureSpec封装了父布局传递给子布局的布局要求。一个MeasureSpec由大小和模式组成。
它有三种模式:
EXACTLY(精确): 父控件决定子控件的确切大小,子控件将被限定在给定的边界里而忽略它本身大小
AT_MOST(最多): 子控件至多达到指定大小的值,
UNSPECIFIED(未指定): 父控件不对子控件施加任何束缚,子元素可以得到任意想要的大小
最简单的映射关系是:
- wrap_parent -> MeasureSpec.AT_MOST
- match_parent -> MeasureSpec.EXACTLY
- 具体值 -> MeasureSpec.EXACTLY
4、基本实现
自定义属性 定义(res/values/attrs.xml 下)
使用,(res/values/style.xml 下)
重写构造方法、onMeasure方法
public class SelfView extends View {
private final int DEFAULT_COLOR= Color.RED;
private final int DEFAULT_SIZE= 320;
private int color;
private int length;
/**
* 一般在直接New一个View的时候调用。
*/
public SelfView(Context context) {
this(context,null);
}
/**
* 一般在layout文件中使用的时候会调用,关于它的所有属性(包括自定义属性)都会包含在attrs中传递进来。
*/
public SelfView(Context context, AttributeSet attrs) {
this(context, attrs,R.attr.defaultStyle);
}
public SelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//使用TypedArray 读取属性值
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.SelfView,defStyleAttr,R.style.defaultResStyle);
color=typedArray.getColor(R.styleable.SelfView_innerColor,DEFAULT_COLOR);
typedArray.recycle();
init(context);
}
/* public SelfView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}*/
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getMeasureSpec(widthMeasureSpec),getMeasureSpec(heightMeasureSpec));
}
private int getMeasureSpec(int widthMeasureSpec) {
int specMode=MeasureSpec.getMode(widthMeasureSpec);
int specSize=MeasureSpec.getSize(widthMeasureSpec);
int result=length;
switch (specMode){
case MeasureSpec.EXACTLY:
result=specSize;
break;
case MeasureSpec.AT_MOST:
result=Math.min(specSize,length);
break;
case MeasureSpec.UNSPECIFIED:
break;
}
return result;
}
}
参考:安卓自定义View进阶-分类与流程、深入理解View的构造函数、理解View的构造函数
2、绘图有关的类
1、Paint
Paint类用于定义绘图时的参数,主要包含颜色、文本、图形样式、位图模式、滤镜等几个方面。
1、图形样式包含绘制的图形是空心样式还是实心样式,同时还能指定落笔和收笔时的笔触效果。
Paint类与图形样式相关的方法有:
setStyle(Paint.Style style):设置绘制的图形是空心样式还是实心样式,默认为实心样式。style 的可选值有:
FILL 实心样式
FILL_AND_STROKE 同时使用实心样 式和空心样式
STROKE 空心样式,绘制时只有线条而无填充效果
setStrokeJoin(Paint.Join join):当绘图样式为 STROKE、FILL_AND_STROKE 时,该方法用于指定线条连接处的拐角样式,能使绘制的图形 更加平滑。默认值为 MITER。可选值如下:
MITER
BEVEL
ROUND
- setStrokeCap(Paint.Cap cap):该方法用于设置落笔时的样式,控制我们的画笔在离开画板时留下的最后一点图形,可选值如下:
BUTT,ROUND, SQUARE
参考:Android自定义组件开发详解 pdf
2、弧度和角度
角度=(弧度/Math.PI)*180
弧度转化为角度:Math.toDegrees()
角度转化为弧度:Math.toRadians()
反正切函數:Math.atan(),返回值是弧度
正切函数:Math.tan(),參數是弧度。
3、Canvas 的操作
⑴位移(translate)
translate(float dx, float dy)
translate是坐标系的移动,是基于当前位置的移动,而不是每次基于屏幕左上角的(0,0)点移动
⑵缩放(scale)
scale(float sx, float sy)
scale(float sx, float sy, float px, float py)
缩放的中心默认为坐标原点 ,当缩放比例为负数的时候会根据缩放中心轴进行翻转
⑶旋转(rotate)
rotate(float degrees)
rotate(float degrees, float px, float py)
默认的旋转中心依旧是坐标原点。
所有的画布操作都只影响后续的绘制,对之前已经绘制过的内容没有影响。
(4)、保存save、回滚restore
大多数情况下只需要记住下面的步骤就可以了:
save(); //保存状态
... //具体操作
restore(); //回滚到之前的状态
画布的操作是不可逆的,而且很多画布操作会影响后续的步骤,所以会对画布的一些状态进行保存和回滚。
通过旋转画布,可以得到如下效果:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.GRAY);
canvas.save();
canvas.translate(width/2,height/2);
canvas.drawCircle(0,0,outerRadius,linePaint);
if(desBitmap!=null && !desBitmap.isRecycled()){
for (int i=0;i<6;i++){
canvas.drawBitmap(desBitmap,-innerRadius,outerRadius-innerRadius,null);
canvas.rotate(60);
}
}
canvas.restore();
}
参考:安卓自定义View进阶-Canvas之画布操作
4、图层
canvas默认就有一个layer,当我们平时调用canvas的各种drawXXX()方法时,其实是把所有的东西都绘制到canvas这个默认的layer上面。
我们还可以通过canvas.saveLayer()新建一个layer,新建的layer放置在canvas默认layer的上部,当我们执行了canvas.saveLayer()之后,我们所有的绘制操作都绘制到了我们新建的layer上
用canvas.saveLayer()方法产生的layer所有像素的ARGB值都是(0,0,0,0),即canvas.saveLayer()方法产生的layer初始时时完全透明的。
canvas.saveLayer()方法会返回一个int值,用于表示layer的ID,在我们对这个新layer绘制完成后可以通过调用canvas.restoreToCount(layer)或者canvas.restore()把这个layer绘制到canvas默认的layer上去,这样就完成了一个layer的绘制工作。
参考:Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解