現(xiàn)在網(wǎng)上有很多自定義view實現(xiàn)日歷的demo,今天講一講如何自己實現(xiàn)這個自定義view。
目前成都創(chuàng)新互聯(lián)公司已為上千多家的企業(yè)提供了網(wǎng)站建設(shè)、域名、虛擬主機(jī)、網(wǎng)站托管、企業(yè)網(wǎng)站設(shè)計、洛寧網(wǎng)站維護(hù)等服務(wù),公司將堅持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
看一下最終效果圖:
在這個自定義view中,我使用了各種奇技淫巧的方法來實現(xiàn)這個日歷,真是費(fèi)盡心思。廢話少說,開始進(jìn)坑。
界面分析
頭部是一個textview,顯示年份和月份,然后下邊一行是星期幾,這兩行可以固定住,不隨月份切換而進(jìn)出屏幕。
再下邊就是我們自定義view 的主角,每個月的天數(shù)。目前規(guī)定是星期日為每星期第一天。上個月的天數(shù)填充滿第一行,下個月的前幾天填充完最后一行,顏色設(shè)置為灰色,本月日期中的周一至周五設(shè)置為紅色,周六周日設(shè)置為青色,特殊日期設(shè)置為綠色,并且在右上角填充特殊標(biāo)識符,用四分之三的圓弧包裹(上個月和下個月的日期沒有)。
此處還有個小細(xì)節(jié),每月的總行數(shù)會不斷改變,但是view的總高度并未改變,所以視覺效果會不一樣。
構(gòu)造方法
public MyCalendar(Context context) { super(context); } public MyCalendar(Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
主要是實現(xiàn)上面兩個構(gòu)造方法,第一個是用來在java代碼中使用的,第二個是用來在xml布局文件中使用的。
暴露的接口
目前接口共有下面幾個,setDate(CustomDate customDate),setWeekendHighLight(boolean b),setSpecialDay(int[] ints)
其中第一個是必須要設(shè)置的,否則是不會顯示任何東西,第二個設(shè)置的是否周末高亮,第三個設(shè)置的是特殊顯示的日期,第四個是設(shè)置是否可以點(diǎn)擊前一個月或者后一個月的日期,默認(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號是星期" + 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)擊前一個月和后一個月的日期 * * @param b */ public void setCanClickNextOrPreMonth(boolean b) { this.canClickNextOrPreMonth = b; }
在這里說明一下計算顯示行數(shù)的方法,首先要注意我們獲取的星期數(shù)與實際的星期幾會有一個增加一天的問題,也就是當(dāng)前是星期4,那么你獲取的int將會是5.
/** * 獲得應(yīng)該設(shè)置為多少行 * * @return */ private int calculateLineNum() { monthDaySum = date.getTotalDayOfMonth(); return (firstDayOfWeek - 1 + monthDaySum) / 7; }
我們將第一天是星期幾減去一后加上這個月總共多少天,就可以獲得最后一天是在什么位置,然后除以七取商的整數(shù)部分,然后在進(jìn)一法即可獲得應(yīng)該顯示多少行。
onSizechanged方法
onSizechanged方法中已經(jīng)可以獲得顯示的尺寸了,此時我們需要做一些工作:
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)來,方便后邊使用。
cutGrid()方法是將區(qū)域分割為行X列的格式。
init()方法初始化了一些畫筆。
setCellDay()方法將每月的天對應(yīng)過到坐標(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是一個常量7,表示每周7天;cellHeight是每行的高度,linecount是一個變量,需要我們根據(jù)日期計算,后邊會說到;radius是我們繪制區(qū)域的半徑,這個值是我們?nèi)挾群透叨戎休^小的值的一半。然后我們將每個方格中心坐標(biāo)點(diǎn)利用雙重循環(huán)放入一個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.FILL); selectTextPaint = new Paint(); selectTextPaint.setColor(Color.WHITE); selectTextPaint.setAntiAlias(true); selectTextPaint.setTextSize(radius / 2); selectTextPaint.setStyle(Paint.Style.FILL); }
基本都是畫筆工具。
然后是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); } } } }
在這里我們用到了一個自定的類-CellDay。
CellDay有以下幾個字段
private String date; private DayState dayState; private CustomDate customDate; private float pointX; private float pointY; private boolean isSelected;
1、String date表示當(dāng)前的日期。
2、dayState是一個美劇類型,定義了天的狀態(tài)值。
其中可以設(shè)置多種狀態(tài),用法和SPECIALDAY基本一樣。
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); } /** * 獲取上個月的天數(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); } /** * 獲取這個月總共的天數(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; } }
注釋中對每個方法的說明已經(jīng)非常清晰了。
int getLastMonthDays()
獲取上個月的天數(shù)是用來計算上個月最后一天是星期幾,然后以此推導(dǎo)出上個月在本月中顯示的天數(shù)和對應(yīng)的星期。
getFirstDayOfWeek()
獲取本月第一天是星期幾,然后排序本月的天數(shù)與對應(yīng)的星期。
int getTotalDayOfMonth()
獲取本月總共多少天。配合第一天是星期幾用來計算總共分為幾行,也就是確定linenumber。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。