最近笔者的朋友需要写一个关于考勤的日历,效果如下,但在网上找了半天都找不到合适的,于是乎就向笔者求助,笔者本来觉得在晚上随便找个日历demo,然后随便的修改几下就可以完成,于是顺口答应,找来几个发现代码修改起来都比较麻烦,于是乎就想为何不自己写一个可以修改起来很方便的日历控件呢 ?那岂不是以后用起来也很方便么 ?于是乎笔者就动手写了一个日记控件,运行起来效果图如下,demo下载地址看完效果图我们就来谈一下笔者自己写自定义控件的思路
###自定义控件思路
笔者在写自定义控件的时候一般采用MVC模式,即是模型(model)-视图(view)-控制器(controller),首先我们分析一下model
####建立模型
建立模型之前,首先我们分析一下日历控件,以便于建立模型,先看图
我们把一个方框看做一个对象,要确定这个对象我们需要确定那些关键属性呢 ?
/**
* 日期的类
* Created by xiaozhu on 2016/8/7.
*/
public class Day {
/**
* 单个日期格子的宽
*/
public int width;
/**
* 单个日期格子的高
*/
public int height;
/**
* 日期的文本
*/
public String text;
/**
* 文本字体的颜色
*/
public int textClor;
/**
* 日期背景的类型 0代表无任何背景,1代表正常打卡,2代表所选日期,3代表当前日期 4,代表即是当前日期,也被选中
*/
public int backgroundStyle;
/**
* 字体的大小
*/
public float textSize;
/**
* 背景的半径
*/
public int backgroundR;
/**
* 出勤的类型 0为不画,1为正常考勤,2为异常,3为出差外出灯
*/
public int workState;
/**
* 出勤状态的半径
*/
public int workStateR = 5;
/**
* 字体在第几行
*/
public int location_x;
/**
* 字体在第几列
*/
public int location_y;
/**
* 创建日期对象
* @param width 每个日期格子的宽度
* @param height 每个日期格子的高度
*/
public Day(int width, int height) {
this.width = width;
this.height = height;
}
定义完我们所需要的属性之后再给day添加一个可以画自己的方法,这样一个完整的模型才算完成,画自己的方法如下
/**
* 画天数
*
* @param canvas 要画的画布
* @param paint 画笔
* @param context 画布的上下文对象
*/
public void drawDays(Canvas canvas, Context context, Paint paint) {
//取窄的边框为圆的半径
backgroundR = width > height ? height : width;
//画背景
drawBackground(canvas, paint);
//画数字
drawTaxt(canvas, paint);
//画考勤
drawWorkState(canvas, paint);
}
/**
* 画考勤
*
* @param canvas
* @param paint
*/
private void drawWorkState(Canvas canvas, Paint paint) {
//确定圆心位置
float cx = location_x * width + width / 2;
float xy = location_y * height + height * 44 / 60;
paint.setStyle(Paint.Style.FILL);
//根据工作状态设置画笔颜色
if (workState == 0) {
return;
}
switch (workState) {
case 1:
paint.setColor(0xFF46CC6E);
break;
case 2:
paint.setColor(0xFFF16269);
break;
case 3:
paint.setColor(0xFF3E81ED);
break;
}
canvas.drawCircle(cx, xy, workStateR, paint);
}
/**
* 花数字
*
* @param canvas
* @param paint
*/
private void drawTaxt(Canvas canvas, Paint paint) {
//根据圆的半径设置字体的大小
textSize = backgroundR / 3;
paint.setTextSize(textSize);
paint.setColor(textClor);
paint.setStyle(Paint.Style.FILL);
//计算文字的宽度
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
int w = rect.width();
//计算画文字的位置
float x = location_x * width + (width - w) / 2;
float y = location_y * height + (height + textSize/2) / 2;
canvas.drawText(text, x, y, paint);
}
/**
* 画背景
*
* @param canvas
* @param paint
*/
private void drawBackground(Canvas canvas, Paint paint) {
//画背景 根据背景状态设置画笔类型
if (backgroundStyle == 0) {
return;
}
switch (backgroundStyle) {
case 1:
paint.setColor(0xFFECF1F4);
paint.setStyle(Paint.Style.FILL);
break;
case 2:
paint.setStyle(Paint.Style.FILL);
paint.setColor(0xFF457BF4);
break;
case 3:
paint.setColor(0xFF457BF4);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
break;
}
//计算圆心的位置
float cx = location_x * width + width / 2;
float cy = location_y * height + height / 2;
canvas.drawCircle(cx, cy, backgroundR * 9 / 20, paint);
}
这样,一个完整的模型就建立完毕了
####建立控制类
我们现在建立一个控制的类,即DayManager类,并声明几个关键的参数
/**
* 日期的管理类
* Created by xiaozhu on 2016/8/7.
*/
public class DayManager {
/**
* 记录当前的时间
*/
public static String currentTime;
/**
* 当前的日期
*/
private static int current = -1;
/**
* 储存当前的日期
*/
private static int tempcurrent=-1;
/**
*
*/
static String[] weeks = {"日", "一", "二", "三", "四", "五", "六"};
static String[] dayArray = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15",
"16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"};
下面就是日起管理类的关键代码,即创建日期集合,并赋值的过程
/**
* 根据日历对象创建日期集合
*
* @param calendar 根据日历生成月份数据,改变这个就可以改变月数
* @param width 控件的宽度
* @param heigh 控件的高度
* @return 返回的天数的集合
*/
public static List createDayByCalendar(Calendar calendar, int width, int heigh) {
//初始化休息的天数
initRestDays(calendar);
//模拟数据 ,无关紧要,
imitateData();
List days = new ArrayList<>();
Day day = null;
int dayWidth = width / 7;
int dayHeight = heigh / (calendar.getActualMaximum(Calendar.WEEK_OF_MONTH) + 1);
//添加星期对象,里面储存的分别是,日、一、二、三、四、五、六,
for (int i = 0; i < 7; i++) {
day = new Day(dayWidth, dayHeight);
//为星期设置位置,为第0行,
day.location_x = i;
day.location_y = 0;
day.text = weeks[i];
//设置日期颜色
day.textClor = 0xFF699CF0;
days.add(day);
}
int count = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
calendar.set(Calendar.DAY_OF_MONTH, 1);
int firstWeekCount = calendar.getActualMaximum(Calendar.DAY_OF_WEEK);
//生成每一天的对象,其中第i次创建的是第i+1天
for (int i = 0; i < count; i++) {
day = new Day(dayWidth, dayHeight);
day.text = dayArray[i];
calendar.set(Calendar.DAY_OF_MONTH, i + 1);
//设置每个天数的位置,即在表格的第几行第几列
day.location_y = calendar.get(Calendar.WEEK_OF_MONTH);
day.location_x = calendar.get(Calendar.DAY_OF_WEEK) - 1;
//设置日期选择状态
if (i == current - 1) {
day.backgroundStyle = 3;
day.textClor = 0xFF4384ED;
} else if (i == select - 1) {
day.backgroundStyle = 2;
day.textClor = 0xFFFAFBFE;
} else {
day.backgroundStyle = 1;
day.textClor = 0xFF8696A5;
}
//设置工作状态
if (restDays.contains(1 + i)) {
day.workState = 0;
} else if (abnormalDays.contains(i + 1)) {
day.workState = 2;
} else if (outDays.contains(i + 1)) {
day.workState = 3;
} else {
day.workState = 1;
}
days.add(day);
}
return days;
}
/**
* 模拟数据
*/
private static void imitateData() {
abnormalDays.add(2);
abnormalDays.add(11);
abnormalDays.add(16);
abnormalDays.add(17);
abnormalDays.add(23);
outDays.add(8);
outDays.add(9);
outDays.add(18);
outDays.add(22);
}
/**
* 初始化休息的天数 计算休息的天数
*
* @param calendar
*/
private static void initRestDays(Calendar calendar) {
int total = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
for (int i = 0; i < total; i++) {
calendar.set(Calendar.DAY_OF_MONTH, i + 1);
if (calendar.get(Calendar.DAY_OF_WEEK) == 1 || calendar.get(Calendar.DAY_OF_WEEK) == 7) {
restDays.add(i + 1);
}
}
}
好了,一个日期控制类就完成了
####建立view类,对日期进行显示
我们现在建立一个CalendarView类,并让他继承View类,并重写onDraw方法。
/**
* 自定义的日历控件
* Created by xiaozhu on 2016/8/1.
*/
public class CalendarView extends View {
private Context context;
/**
* 画笔
*/
private Paint paint;
/***
* 当前的时间
*/
private Calendar calendar;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取day集合并绘制
List days = DayManager.createDayByCalendar(calendar, getMeasuredWidth(), getMeasuredHeight());
for (Day day : days) {
day.drawDays(canvas, context, paint);
}
}
我们在onDraw里面调用DayManager的createDayByCalendar方法,并把当前控件显示的日历和控件的宽高传进去,获取日期的集合,遍历集合,调用对象的drawDays方法,把画笔和画布穿进去,一个完整的日历就画出来了。最后我们对他进行完善,即添加点击响应事件。下面我们重写onTouchEvent方法
@Override
public boolean onTouchEvent(MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
//判断点击的是哪个日期
float x = event.getX();
float y = event.getY();
//计算点击的是哪个日期
int locationX = (int) (x * 7 / getMeasuredWidth());
int locationY = (int) ((calendar.getActualMaximum(Calendar.WEEK_OF_MONTH) + 1) * y / getMeasuredHeight());
if (locationY == 0) {
return super.onTouchEvent(event);
} else if (locationY == 1) {
calendar.set(Calendar.DAY_OF_MONTH, 1);
System.out.println("xiaozhu" + calendar.get(Calendar.DAY_OF_WEEK) + ":" + locationX);
if (locationX < calendar.get(Calendar.DAY_OF_WEEK) - 1) {
return super.onTouchEvent(event);
}
} else if (locationY == calendar.getActualMaximum(Calendar.WEEK_OF_MONTH)) {
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
if (locationX > calendar.get(Calendar.DAY_OF_WEEK) + 1) {
return super.onTouchEvent(event);
}
}
calendar.set(Calendar.WEEK_OF_MONTH, (int) locationY);
calendar.set(Calendar.DAY_OF_WEEK, (int) (locationX + 1));
DayManager.setSelect(calendar.get(Calendar.DAY_OF_MONTH));
//添加点击监听
if (listener!=null){
listener.selectChange(this,calendar.getTime());
}
invalidate();
}
return super.onTouchEvent(event);
}
好了,一个完整的日历控件就画完了。
####在activity里面使用
创建布局文件,在布局文件添加控件,然后再给控件添加月份标题
在activity里面查找到控件,并通过CaldendarView里面的setCalendar方法来显示calendar代表的月份。
###更改自己想要的视图
####更改统一样式
如果我们想要更改所有布局的样式,只需要给CalendarView
设置回调即可,在方法里面画上自己需要的东西。
,我们需要用到的几个属性是
然后我们就可以确定我们绘图的区域即(0,0 )为区域左上角坐标,(width,height )为右下角坐标,可以在此区域画上任何你想添加的东西,而width ,height ,都是通过DayManager在控制,完全不用你去考虑
####更改特殊样式
如果你想给某一天添加特殊的属性,请参照使用引导
使用引导