如今网上有不少自定义view实现日历的demo,今天讲一讲如何本身实现这个自定义view。javascript
看一下最终效果图:java
在这个自定义view中,我使用了各类奇技淫巧的方法来实现这个日历,真是费尽心思。废话少说,开始进坑。ide
头部是一个textview,显示年份和月份,而后下边一行是星期几,这两行能够固定住,不随月份切换而进出屏幕。工具
再下边就是咱们自定义view 的主角,每月的天数。目前规定是星期日为每星期第一天。上个月的天数填充满第一行,下个月的前几天填充完最后一行,颜色设置为灰色,本月日期中的周一至周五设置为红色,周六周日设置为青色,特殊日期设置为绿色,而且在右上角填充特殊标识符,用四分之三的圆弧包裹(上个月和下个月的日期没有)。布局
此处还有个小细节,每个月的总行数会不断改变,可是view的总高度并未改变,因此视觉效果会不同。ui
public MyCalendar(Context context) {
super(context);
}
public MyCalendar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}复制代码
主要是实现上面两个构造方法,第一个是用来在java代码中使用的,第二个是用来在xml布局文件中使用的。this
目前接口共有下面几个,setDate(CustomDate customDate),setWeekendHighLight(boolean b),setSpecialDay(int[] ints)spa
其中第一个是必需要设置的,不然是不会显示任何东西,第二个设置的是否周末高亮,第三个设置的是特殊显示的日期,第四个是设置是否能够点击前一个月或者后一个月的日期,默认为不设置,后期能够根据本身需求增长其余接口。3d
/** * 暴露接口,设置日期 * * @param customDate */
public void setDate(CustomDate customDate) {
Log.d(TAG, customDate.toString());
this.date = customDate;
firstDayOfWeek = date.getFirstDayOfWeek();
Log.d(TAG, (date.getMonth() + 1) + "月1号是星期" + firstDayOfWeek);
lastDayOfWeek = date.getLastDayOfWeek();
lineCount = calculateLineNum() + 1;
lastMonthTotalDays = date.getLastMonthDays();
}
/** * 暴露接口,设置是否周末高亮 * * @param b */
public void setWeekendHighLight(boolean b) {
this.weekendHighlight = b;
}
public void setSpecialDay(int[] ints) {
this.specialDays = ints;
}
/** * 暴露接口,设置是否能够点击前一个月和后一个月的日期 * * @param b */
public void setCanClickNextOrPreMonth(boolean b) {
this.canClickNextOrPreMonth = b;
}复制代码
在这里说明一下计算显示行数的方法,首先要注意咱们获取的星期数与实际的星期几会有一个增长一天的问题,也就是当前是星期4,那么你获取的int将会是5.code
/** * 得到应该设置为多少行 * * @return */
private int calculateLineNum() {
monthDaySum = date.getTotalDayOfMonth();
return (firstDayOfWeek - 1 + monthDaySum) / 7;
}复制代码
咱们将第一天是星期几减去一后加上这个月总共多少天,就能够得到最后一天是在什么位置,而后除以七取商的整数部分,而后在进一法便可得到应该显示多少行。
onSizechanged方法中已经能够得到显示的尺寸了,此时咱们须要作一些工做:
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
this.viewWidth = w;
this.viewHeight = h;
Log.d(TAG, "onSizeChanged" + w + h);
cutGrid();
init();
setCellDay();
}复制代码
首先是将宽和高引入进来,方便后边使用。
cutGrid()方法是将区域分割为行X列的格式。
init()方法初始化了一些画笔。
setCellDay()方法将每个月的天对应过到坐标上。
首先看一下cutGrid()方法:
/** * 切分为天天 */
private void cutGrid() {
cellWidth = (float) viewWidth / ROW_COUNT;
cellHeight = (float) viewHeight / lineCount;
this.radius = Math.min(cellWidth / 2, cellHeight / 2);
for (int i = 0; i < lineCount; i++) {
for (int j = 0; j < ROW_COUNT; j++) {
points.add(new PointF(cellWidth * j + cellWidth / 2, cellHeight * i + cellHeight / 2));
}
}
}复制代码
cellWidth是天天的宽度,其中ROW_COUNT是一个常量7,表示每周7天;cellHeight是每行的高度,linecount是一个变量,须要咱们根据日期计算,后边会说到;radius是咱们绘制区域的半径,这个值是咱们取宽度和高度中较小的值的一半。而后咱们将每一个方格中心坐标点利用双重循环放入一个List
整个view被分割为如上的形状。
下面来看一下init()方法:
private void init() {
circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setAntiAlias(true);
circlePaint.setColor(Color.BLUE);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(radius / 2);
selectPaint = new Paint();
selectPaint.setColor(Color.YELLOW);
selectPaint.setAlpha(10);
selectPaint.setAntiAlias(true);
selectPaint.setStyle(Paint.Style.STROKE);
selectTextPaint = new Paint();
selectTextPaint.setColor(Color.WHITE);
selectTextPaint.setAntiAlias(true);
selectTextPaint.setTextSize(radius / 2);
selectTextPaint.setStyle(Paint.Style.FILL);
}复制代码
基本都是画笔工具。
而后是setAllDays()方法:
/** * 设置总共显示多少天,天天的状态 */
private void setCellDay() {
cellDays = new CellDay[lineCount * ROW_COUNT];
for (int i = 0, length = cellDays.length; i < length; i++) {
cellDays[i] = new CellDay();
cellDays[i].setPointX(points.get(i).x);
cellDays[i].setPointY(points.get(i).y);
if (firstDayOfWeek > 1 && i < firstDayOfWeek - 1) {
cellDays[i].setDayState(DayState.LASTMONTH);
cellDays[i].setDate(String.valueOf(lastMonthTotalDays - firstDayOfWeek + i + 2));
cellDays[i].setCustomDate(new CustomDate(
date.getYear(), date.getMonth() - 1, lastMonthTotalDays - firstDayOfWeek + i + 2));
}
if (i >= firstDayOfWeek - 1 && i < monthDaySum + firstDayOfWeek - 1) {
cellDays[i].setDayState(CURRENTMONTH);
cellDays[i].setDate(String.valueOf(i + 2 - firstDayOfWeek));
cellDays[i].setCustomDate(new CustomDate(
date.getYear(), date.getMonth(), i - firstDayOfWeek + 2));
//设置周末高亮
if (weekendHighlight) {
if (i % 7 == 0 || i % 7 == 6) {
cellDays[i].setDayState(WEEKEND);
}
}
}
if (i >= monthDaySum + firstDayOfWeek - 1) {
cellDays[i].setDayState(NEXTMONTH);
cellDays[i].setDate(String.valueOf(i - monthDaySum - firstDayOfWeek + 2));
cellDays[i].setCustomDate(new CustomDate(
date.getYear(), date.getMonth() + 1, i - monthDaySum - firstDayOfWeek + 2));
}
for (int j = 0, s = specialDays.length; j < s; j++) {
if (specialDays[j] + firstDayOfWeek - 2 == i) {
cellDays[i].setDayState(SPECIALDAY);
}
}
}
}复制代码
在这里咱们用到了一个自定的类-CellDay。
CellDay有如下几个字段
private String date;
private DayState dayState;
private CustomDate customDate;
private float pointX;
private float pointY;
private boolean isSelected;复制代码
其中能够设置多种状态,用法和SPECIALDAY基本同样。
public class CustomDate {
private Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
private int year;
private int month;
private int day;
private int dayOfWeek;
public CustomDate() {
}
/** * 获取当前的日期 * @return */
public CustomDate getCurrentDate() {
this.year = calendar.get(Calendar.YEAR);
this.month = calendar.get(Calendar.MONTH);
this.day = calendar.get(Calendar.DAY_OF_MONTH);
this.dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
return new CustomDate(year, month, day);
}
public CustomDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
calendar.set(year, month, day);
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
}
/** * 获取上个月的天数 * @return */
public int getLastMonthDays() {
return this.getDaysOfMonth(this.year, this.month - 1);
}
/** * 获取第一天是星期几 * * @return */
public int getFirstDayOfWeek() {
calendar.set(this.year, this.month, 1);
return calendar.get(Calendar.DAY_OF_WEEK);
}
/** * 获取最后一天是星期几 * * @return */
public int getLastDayOfWeek() {
calendar.set(this.year, this.month, getTotalDayOfMonth());
return calendar.get(Calendar.DAY_OF_WEEK);
}
/** * 获取这个月总共的天数 * @return */
public int getTotalDayOfMonth() {
return this.getDaysOfMonth(year, month);
}
public int getTotalWeekOfMonth() {
return calendar.getMaximum(Calendar.WEEK_OF_MONTH);
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
@Override
public String toString() {
return "CustomDate{" +
"year=" + year +
", month=" + (getMonth() + 1) +
", day=" + day +
", dayOfWeek=" + dayOfWeek +
'}';
}
/** * 获取年中每个月的天数 * @param year * @param month * @return */
private int getDaysOfMonth(int year, int month) {
if (month > 11) {
month = 0;
year += 1;
} else if (month < 0) {
month = 11;
year -= 1;
}
int[] arr = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int daysOfMonth = 0;
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
arr[1] = 29;
}
daysOfMonth = arr[month];
return daysOfMonth;
}
}复制代码
注释中对每一个方法的说明已经很是清晰了。
下一节将介绍如何绘制和分发touch事件。