目录
一、继承系统控件的自定义View
1、概念
继承控件的意思就是,我们并不需要自己重头去实现一个控件,只需要去继承一个现有的控件,然后在这个控件上增加一些新的功能,就可以形成一个自定义的控件了。这种自定义控件的特点就是不仅能够按照我们的需求加入相应的功能,还可以保留原生控件的所有功能。
2、案例
继承TextView实现一个无效的TextView
1)源码
public class InvalidTextView extends TextView {
private Paint paint;
public InvalidTextView(Context context) {
super(context);
init();
}
public InvalidTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public InvalidTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(3);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
//文字中间画一条线
canvas.drawLine(0, height/2, width, height/2, paint);
}
}
2)效果图
二、继承View的自定义View
1、概念
所有的绘制逻辑和流程都需要自己完成。
2、案例
实现自定义时钟
1)圆上坐标点分析
圆心用(centerCircleX, centerCircleY)表示,radius表示半径。
可使用Math.sin(double a)和Math.cos(double a)得到sinθ和cosθ的值。
注意:a代表弧长,非度数,圆的弧长为2兀
一共需要画60个指针,即每份指针指针对应的弧长 = 2 * Math.PI / 60
即圆上坐标点表示为(centerCircleX + radius * sinθ, centerCircleY + radius * cosθ)
//画刻度
for (int i = 0; i < 60; i++) {
float x1, y1, x2, y2;//刻度两端的坐标,即起始到结束的坐标
float scale;//每条线离圆心的最近端坐标点到圆心的距离
Double du = 2 * Math.PI / 60 * i;//当前所占角度的弧长
Double sinx = Math.sin(du);//该弧长的sin值
Double cosy = Math.cos(du);//该弧长的cos值
x1 = (float) (centerCircleX + radius * sinx);
y1 = (float) (centerCircleY - radius * cosy);
if (i % 5 == 0) {//筛选长刻度
scale = 6 * radius / 7;
x2 = (float) (centerCircleX + scale * sinx);
y2 = (float) (centerCircleY - scale * cosy);
canvas.drawLine(x1, y1, x2, y2, longScalePaint);
} else {//筛选短刻度
scale = 9 * radius / 10;
x2 = (float) (centerCircleX + scale * sinx);
y2 = (float) (centerCircleY - scale * cosy);
canvas.drawLine(x1, y1, x2, y2, shortScalePaint);
}
}
2)绘制数字1~12
/**
* text:绘制的文字
* x:x坐标
* y:y坐标(基线)
*/
public void drawText(String text, float x, float y, Paint paint)
drawText(String text, float x, float y, Paint paint) ,其中y是绘制的基线并不是文字中心点,即默认是从图中左下角红点开始,直接使用drawText会导致错位。
可通过paint.getTextBounds(String text, int start, int end, Rect bounds)获取每个数字被全部包裹的最小的矩形边框数值,再通过坐标移动到相应位置。
//绘制长刻度上的数字1~12
if (num > 12) {
num = 1;
}
float numScale = 3 * radius / 4;
float numX = (float) (centerCircleX + numScale * sinx);
float numY = (float) (centerCircleY - numScale * cosy);
String number = num + "";
numPaint.getTextBounds(number, 0, number.length(), textBound);
canvas.drawText(number, numX - textBound.width() / 2, numY + textBound.height() / 2, numPaint);
num = num + 1;
3)绘制指针
先获取系统时分秒
//获取当前时分秒
float second = calendar.get(Calendar.SECOND);
float minute = calendar.get(Calendar.MINUTE);
float hour = calendar.get(Calendar.HOUR) + minute / 60;
再进行绘制
//绘制时分秒指针
float sencondLength = 7 * radius / 10;//秒针长度
float minuteLength = 1 * radius / 2;//分针长度
float hourLength = 3 * radius / 10;//时针长度
float secondX, secondY, minuteX, minuteY, hourX, hourY;
secondX = (float) (centerCircleX + sencondLength * Math.sin(2 * Math.PI / 60 * second));
secondY = (float) (centerCircleY - sencondLength * Math.cos(2 * Math.PI / 60 * second));
minuteX = (float) (centerCircleX + minuteLength * Math.sin(2 * Math.PI / 60 * minute));
minuteY = (float) (centerCircleY - minuteLength * Math.cos(2 * Math.PI / 60 * minute));
hourX = (float) (centerCircleX + hourLength * Math.sin(2 * Math.PI / 12 * hour));
hourY = (float) (centerCircleY - hourLength * Math.cos(2 * Math.PI / 12 * hour));
canvas.drawLine(centerCircleX, centerCircleY, secondX, secondY, secondPaint);
canvas.drawLine(centerCircleX, centerCircleY, minuteX, minuteY, minutePaint);
canvas.drawLine(centerCircleX, centerCircleY, hourX, hourY, hourPaint);
需要每秒重绘一次
//每隔1秒重绘View,重绘会调用onDraw()方法
postInvalidateDelayed(1000);
4)源码
public class AlarmClockView extends View {
private Paint circlePaint, centerCirclePaint;
private Paint longScalePaint, shortScalePaint;
private Paint numPaint;
private Paint hourPaint, minutePaint, secondPaint;
private int height, width, radius;
private Rect textBound = new Rect();
private Calendar calendar;
public AlarmClockView(Context context) {
super(context);
init();
}
public AlarmClockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public AlarmClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
circlePaint = new Paint();
centerCirclePaint = new Paint();
longScalePaint = new Paint();
shortScalePaint = new Paint();
numPaint = new Paint();
hourPaint = new Paint();
minutePaint = new Paint();
secondPaint = new Paint();
//大圆
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeWidth(5f);
//长刻度
longScalePaint.setStrokeWidth(2f);
//数字
numPaint.setTextSize(25);
//时针
hourPaint.setStrokeWidth(5f);
//分针
minutePaint.setStrokeWidth(3f);
//秒针
secondPaint.setStrokeWidth(2f);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
calendar = Calendar.getInstance();
//得到控件的宽高
height = getHeight();
width = getWidth();
//以最短的一边为所要绘制大圆形的半径
if (height > width) {
radius = width / 2;
} else {
radius = height / 2;
}
//画笔有加粗,固在半径的基础上减10
radius = radius - 10;
//圆心
int centerCircleX = width / 2;
int centerCircleY = height / 2;
//画大圆
canvas.drawCircle(centerCircleX, centerCircleY, radius, circlePaint);
//画中心小圆
canvas.drawCircle(centerCircleX, centerCircleY, 15, centerCirclePaint);
//长刻度要显示的数字,从12开始顺时针绘制
int num = 12;
//画刻度
for (int i = 0; i < 60; i++) {
float x1, y1, x2, y2;//刻度两端的坐标,即起始到结束的坐标
float scale;//每条线离圆心的最近端坐标点到圆心的距离
Double du = 2 * Math.PI / 60 * i;//当前所占角度的弧长
Double sinx = Math.sin(du);//该弧长的sin值
Double cosy = Math.cos(du);//该弧长的cos值
x1 = (float) (centerCircleX + radius * sinx);
y1 = (float) (centerCircleY - radius * cosy);
if (i % 5 == 0) {//筛选长刻度
scale = 6 * radius / 7;
x2 = (float) (centerCircleX + scale * sinx);
y2 = (float) (centerCircleY - scale * cosy);
canvas.drawLine(x1, y1, x2, y2, longScalePaint);
//绘制长刻度上的数字1~12
if (num > 12) {
num = 1;
}
float numScale = 3 * radius / 4;
float numX = (float) (centerCircleX + numScale * sinx);
float numY = (float) (centerCircleY - numScale * cosy);
String number = num + "";
numPaint.getTextBounds(number, 0, number.length(), textBound);
canvas.drawText(number, numX - textBound.width() / 2, numY + textBound.height() / 2, numPaint);
num = num + 1;
} else {//筛选短刻度
scale = 9 * radius / 10;
x2 = (float) (centerCircleX + scale * sinx);
y2 = (float) (centerCircleY - scale * cosy);
canvas.drawLine(x1, y1, x2, y2, shortScalePaint);
}
}
//获取当前时分秒
float second = calendar.get(Calendar.SECOND);
float minute = calendar.get(Calendar.MINUTE);
float hour = calendar.get(Calendar.HOUR) + minute / 60;
//绘制时分秒指针
float sencondLength = 7 * radius / 10;//秒针长度
float minuteLength = 1 * radius / 2;//分针长度
float hourLength = 3 * radius / 10;//时针长度
float secondX, secondY, minuteX, minuteY, hourX, hourY;
secondX = (float) (centerCircleX + sencondLength * Math.sin(2 * Math.PI / 60 * second));
secondY = (float) (centerCircleY - sencondLength * Math.cos(2 * Math.PI / 60 * second));
minuteX = (float) (centerCircleX + minuteLength * Math.sin(2 * Math.PI / 60 * minute));
minuteY = (float) (centerCircleY - minuteLength * Math.cos(2 * Math.PI / 60 * minute));
hourX = (float) (centerCircleX + hourLength * Math.sin(2 * Math.PI / 12 * hour));
hourY = (float) (centerCircleY - hourLength * Math.cos(2 * Math.PI / 12 * hour));
canvas.drawLine(centerCircleX, centerCircleY, secondX, secondY, secondPaint);
canvas.drawLine(centerCircleX, centerCircleY, minuteX, minuteY, minutePaint);
canvas.drawLine(centerCircleX, centerCircleY, hourX, hourY, hourPaint);
//每隔1秒重绘View,重绘会调用onDraw()方法
postInvalidateDelayed(1000);
}
}