現(xiàn)在網(wǎng)上有很多自定義view實(shí)現(xiàn)日歷的demo造挽,今天講一講如何自己實(shí)現(xiàn)這個(gè)自定義view。
看一下最終效果圖:
在這個(gè)自定義view中香罐,我使用了各種奇技淫巧的方法來(lái)實(shí)現(xiàn)這個(gè)日歷肮街,真是費(fèi)盡心思。廢話(huà)少說(shuō)像棘,開(kāi)始進(jìn)坑。
界面分析
頭部是一個(gè)textview诊县,顯示年份和月份讲弄,然后下邊一行是星期幾措左,這兩行可以固定住依痊,不隨月份切換而進(jìn)出屏幕。
再下邊就是我們自定義view 的主角怎披,每個(gè)月的天數(shù)胸嘁。目前規(guī)定是星期日為每星期第一天。上個(gè)月的天數(shù)填充滿(mǎn)第一行凉逛,下個(gè)月的前幾天填充完最后一行性宏,顏色設(shè)置為灰色,本月日期中的周一至周五設(shè)置為紅色状飞,周六周日設(shè)置為青色毫胜,特殊日期設(shè)置為綠色,并且在右上角填充特殊標(biāo)識(shí)符诬辈,用四分之三的圓弧包裹(上個(gè)月和下個(gè)月的日期沒(méi)有)酵使。
此處還有個(gè)小細(xì)節(jié),每月的總行數(shù)會(huì)不斷改變焙糟,但是view的總高度并未改變口渔,所以視覺(jué)效果會(huì)不一樣。
構(gòu)造方法
public MyCalendar(Context context) {
super(context);
}
public MyCalendar(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
主要是實(shí)現(xiàn)上面兩個(gè)構(gòu)造方法穿撮,第一個(gè)是用來(lái)在java代碼中使用的缺脉,第二個(gè)是用來(lái)在xml布局文件中使用的痪欲。
暴露的接口
目前接口共有下面幾個(gè),setDate(CustomDate customDate)攻礼,setWeekendHighLight(boolean b)业踢,setSpecialDay(int[] ints)
其中第一個(gè)是必須要設(shè)置的,否則是不會(huì)顯示任何東西礁扮,第二個(gè)設(shè)置的是否周末高亮陨亡,第三個(gè)設(shè)置的是特殊顯示的日期,第四個(gè)是設(shè)置是否可以點(diǎn)擊前一個(gè)月或者后一個(gè)月的日期深员,默認(rèn)為不設(shè)置负蠕,后期可以根據(jù)自己需求增加其他接口。
/**
* 暴露接口倦畅,設(shè)置日期
*
* @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號(hào)是星期" + firstDayOfWeek);
lastDayOfWeek = date.getLastDayOfWeek();
lineCount = calculateLineNum() + 1;
lastMonthTotalDays = date.getLastMonthDays();
}
/**
* 暴露接口遮糖,設(shè)置是否周末高亮
*
* @param b
*/
public void setWeekendHighLight(boolean b) {
this.weekendHighlight = b;
}
public void setSpecialDay(int[] ints) {
this.specialDays = ints;
}
/**
* 暴露接口,設(shè)置是否可以點(diǎn)擊前一個(gè)月和后一個(gè)月的日期
*
* @param b
*/
public void setCanClickNextOrPreMonth(boolean b) {
this.canClickNextOrPreMonth = b;
}
在這里說(shuō)明一下計(jì)算顯示行數(shù)的方法,首先要注意我們獲取的星期數(shù)與實(shí)際的星期幾會(huì)有一個(gè)增加一天的問(wèn)題叠赐,也就是當(dāng)前是星期4欲账,那么你獲取的int將會(huì)是5.
/**
* 獲得應(yīng)該設(shè)置為多少行
*
* @return
*/
private int calculateLineNum() {
monthDaySum = date.getTotalDayOfMonth();
return (firstDayOfWeek - 1 + monthDaySum) / 7;
}
我們將第一天是星期幾減去一后加上這個(gè)月總共多少天,就可以獲得最后一天是在什么位置芭概,然后除以七取商的整數(shù)部分赛不,然后在進(jìn)一法即可獲得應(yīng)該顯示多少行。
onSizechanged方法
onSizechanged方法中已經(jīng)可以獲得顯示的尺寸了罢洲,此時(shí)我們需要做一些工作:
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();
}
首先是將寬和高引入進(jìn)來(lái)踢故,方便后邊使用。
cutGrid()方法是將區(qū)域分割為行X列的格式惹苗。
init()方法初始化了一些畫(huà)筆殿较。
setCellDay()方法將每月的天對(duì)應(yīng)過(guò)到坐標(biāo)上。
首先看一下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是一個(gè)常量7淋纲,表示每周7天;cellHeight是每行的高度院究,linecount是一個(gè)變量洽瞬,需要我們根據(jù)日期計(jì)算,后邊會(huì)說(shuō)到业汰;radius是我們繪制區(qū)域的半徑伙窃,這個(gè)值是我們?nèi)挾群透叨戎休^小的值的一半。然后我們將每個(gè)方格中心坐標(biāo)點(diǎn)利用雙重循環(huán)放入一個(gè)List<Point> points中蔬胯。
整個(gè)view被分割為如上的形狀对供。
下面來(lái)看一下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.FILL);
selectTextPaint = new Paint();
selectTextPaint.setColor(Color.WHITE);
selectTextPaint.setAntiAlias(true);
selectTextPaint.setTextSize(radius / 2);
selectTextPaint.setStyle(Paint.Style.FILL);
}
基本都是畫(huà)筆工具。
然后是setAllDays()方法:
/**
* 設(shè)置總共顯示多少天,每天的狀態(tài)
*/
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));
//設(shè)置周末高亮
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);
}
}
}
}
在這里我們用到了一個(gè)自定的類(lèi)-CellDay产场。
CellDay有以下幾個(gè)字段
private String date;
private DayState dayState;
private CustomDate customDate;
private float pointX;
private float pointY;
private boolean isSelected;
- String date表示當(dāng)前的日期鹅髓。
- dayState是一個(gè)美劇類(lèi)型,定義了天的狀態(tài)值京景。
LASTMONTH:上個(gè)月的日期
CURRENTMONTH:本月的日期
NEXTMONTH: 下個(gè)月的日期
CURRENTDAY: 今天的日期
WEEKEND:周末的日期
SPECIALDAY:用戶(hù)自定義的可以設(shè)置狀態(tài)的日期
其中可以設(shè)置多種狀態(tài)窿冯,用法和SPECIALDAY基本一樣。
- cusomedate是我們自己定義的一個(gè)工具類(lèi)确徙,包含項(xiàng)目中需要用到的一系列方法醒串。
- pointX是橫坐標(biāo)。
- pointY是縱坐標(biāo)鄙皇。
- isSelceted表示有沒(méi)有被選中芜赌。
CustomDate工具
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() {
}
/**
* 獲取當(dāng)前的日期
* @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);
}
/**
* 獲取上個(gè)月的天數(shù)
* @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);
}
/**
* 獲取這個(gè)月總共的天數(shù)
* @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 +
'}';
}
/**
* 獲取年中每月的天數(shù)
* @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;
}
}
注釋中對(duì)每個(gè)方法的說(shuō)明已經(jīng)非常清晰了。
- int getLastMonthDays()
獲取上個(gè)月的天數(shù)是用來(lái)計(jì)算上個(gè)月最后一天是星期幾伴逸,然后以此推導(dǎo)出上個(gè)月在本月中顯示的天數(shù)和對(duì)應(yīng)的星期缠沈。 - getFirstDayOfWeek()
獲取本月第一天是星期幾,然后排序本月的天數(shù)與對(duì)應(yīng)的星期错蝴。 - int getTotalDayOfMonth()
獲取本月總共多少天洲愤。配合第一天是星期幾用來(lái)計(jì)算總共分為幾行,也就是確定linenumber顷锰。
下一節(jié)將介紹如何繪制和分發(fā)touch事件柬赐。