先來效果圖:

月曆部分參考了網絡上找到的一個月曆類,裡面抽象出來了好多方法,基本上滿足了對簽到的需求,如有需要也可以根據項目實際情況進行定制。
public class SignCalendar extends ViewFlipper implements android.view.GestureDetector.OnGestureListener
該類繼承自ViewFlipper,能夠很好的實作不同月的月曆之間的切換和動畫,還實作了手勢監聽。
首先介紹一下該類的一些主要方法:
private void drawFrame(LinearLayout oneCalendar);//畫月曆的架構
private void setCalendarDate();//填充月曆,包括日期、背景和标記
public synchronized void nextMonth() ;//滑動時顯示下一個月
public synchronized void lastMonth() ;//滑動時顯示上一個月
//設定和移除日期标記的各種方法
public void addMark(Date date, int id);public void removeMark(Date date);
public void addMark(String date, int id) ; public void removeMark(String date);
public void addMarks(Date[] date, int id);public void removeAllMarks();
public void addMarks(List<String> date, int id);
//設定和移除日期背景的方法
public void setCalendarDayBgColor(Date date, int color); public void removeCalendarDayBgColor(Date date);
public void setCalendarDayBgColor(String date, int color); public void removeCalendarDayBgColor(String date);
public void setCalendarDaysBgColor(List<String> date, int color);public void removeAllBgColor();
public void setCalendarDayBgColor(String[] date, int color);
public boolean hasMarked(String date);
//移除标記和背景設定
public void clearAll();
此外還提供了兩個回調接口:
private OnCalendarClickListener onCalendarClickListener; // 月曆翻頁回調
private OnCalendarDateChangedListener onCalendarDateChangedListener; // 月曆點選回調,根據需要添加
接下來,介紹一下月曆架構的繪制,整個布局是在代碼裡實作的,先看下drawFrame方法吧:
private void drawFrame(LinearLayout oneCalendar) {
// 添加周末線性布局
LinearLayout title = new LinearLayout(getContext());
title.setBackgroundColor(COLOR_BG_WEEK_TITLE);
title.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams layout =
new LinearLayout.LayoutParams(MarginLayoutParams.MATCH_PARENT,
MarginLayoutParams.WRAP_CONTENT, 0.5f);
title.setLayoutParams(layout);
oneCalendar.addView(title);
// 添加周末TextView
for (int i = 0; i < COLS_TOTAL; i++) {
TextView view = new TextView(getContext());
view.setGravity(Gravity.CENTER);
view.setPadding(0, 10, 0, 10);
view.setText(weekday[i]);
view.setTextColor(Color.WHITE);
view.setLayoutParams(new LinearLayout.LayoutParams(0, -1, 1));
title.addView(view);
}
// 添加日期布局
LinearLayout content = new LinearLayout(getContext());
content.setOrientation(LinearLayout.VERTICAL);
content.setLayoutParams(new LinearLayout.LayoutParams(-1, 0, 7f));
oneCalendar.addView(content);
// 添加日期TextView
for (int i = 0; i < ROWS_TOTAL; i++) {
LinearLayout row = new LinearLayout(getContext());
row.setOrientation(LinearLayout.HORIZONTAL);
row.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1));
content.addView(row);
// 繪制月曆上的列
for (int j = 0; j < COLS_TOTAL; j++) {
RelativeLayout col = new RelativeLayout(getContext());
col.setLayoutParams(new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1));
col.setBackgroundResource(R.drawable.calendar_day_bg);
// col.setBackgroundResource(R.drawable.sign_dialog_day_bg);
col.setClickable(false);
row.addView(col);
// 給每一個日子加上監聽
col.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ViewGroup parent = (ViewGroup) v.getParent();
int row = 0, col = 0;
// 擷取列坐标
for (int i = 0; i < parent.getChildCount(); i++) {
if (v.equals(parent.getChildAt(i))) {
col = i;
break;
}
}
// 擷取行坐标
ViewGroup pparent = (ViewGroup) parent.getParent();
for (int i = 0; i < pparent.getChildCount(); i++) {
if (parent.equals(pparent.getChildAt(i))) {
row = i;
break;
}
}
if (onCalendarClickListener != null) {
onCalendarClickListener.onCalendarClick(row, col, dates[row][col]);
}
}
});
}
}
}
從代碼中可以看出,,先用水準方向的線性布局繪制了星期的布局,然後通過往裡面添加textView實作日期的顯示,這就實作了效果圖中星期的顯示。
下面就是繪制某月的月曆布局和添加日期的textview,還給日期增加了點選回調,可以根據需求使用。
日期的架構都有了,接下來就是添加文字、标記、和背景了:
/**
* 填充月曆(包含日期、标記、背景等)
*/
private void setCalendarDate() {
// 根據月曆的日子擷取這一天是星期幾
int weekday = calendarday.getDay();
// 每個月第一天
int firstDay = 1;
// 每個月中間号,根據循環會自動++
int day = firstDay;
// 每個月的最後一天
int lastDay = getDateNum(calendarday.getYear(), calendarday.getMonth());
// 下個月第一天
int nextMonthDay = 1;
int lastMonthDay = 1;
// 填充每一個空格
for (int i = 0; i < ROWS_TOTAL; i++) {
for (int j = 0; j < COLS_TOTAL; j++) {
// 這個月第一天不是禮拜天,則需要繪制上個月的剩餘幾天
if (i == 0 && j == 0 && weekday != 0) {
int year = 0;
int month = 0;
int lastMonthDays = 0;
// 如果這個月是1月,上一個月就是去年的12月
if (calendarday.getMonth() == 0) {
year = calendarday.getYear() - 1;
month = Calendar.DECEMBER;
} else {
year = calendarday.getYear();
month = calendarday.getMonth() - 1;
}
// 上個月的最後一天是幾号
lastMonthDays = getDateNum(year, month);
// 第一個格子展示的是幾号
int firstShowDay = lastMonthDays - weekday + 1;
// 上月
for (int k = 0; k < weekday; k++) {
lastMonthDay = firstShowDay + k;
RelativeLayout group = getDateView(0, k);
group.setGravity(Gravity.CENTER);
TextView view = null;
if (group.getChildCount() > 0) {
view = (TextView) group.getChildAt(0);
} else {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, -1);
view = new TextView(getContext());
view.setLayoutParams(params);
view.setGravity(Gravity.CENTER);
group.addView(view);
}
view.setText(Integer.toString(lastMonthDay));
view.setTextColor(COLOR_TX_OTHER_MONTH_DAY);
dates[0][k] = format(new Date(year, month, lastMonthDay));
// 設定日期背景色
if (dayBgColorMap.get(dates[0][k]) != null) {
view.setBackgroundResource(dayBgColorMap.get(dates[0][k]));
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
// 設定标記
setMarker(group, 0, k);
}
j = weekday - 1;
// 這個月第一天是禮拜天,不用繪制上個月的日期,直接繪制這個月的日期
} else {
RelativeLayout group = getDateView(i, j);
group.setGravity(Gravity.CENTER);
TextView view = null;
if (group.getChildCount() > 0) {
view = (TextView) group.getChildAt(0);
} else {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, -1);
view = new TextView(getContext());
view.setLayoutParams(params);
view.setGravity(Gravity.CENTER);
group.addView(view);
}
// 本月
if (day <= lastDay) {
dates[i][j] = format(new Date(calendarday.getYear(), calendarday.getMonth(), day));
view.setText(Integer.toString(day));
// 當天
if (thisday.getDate() == day && thisday.getMonth() == calendarday.getMonth()
&& thisday.getYear() == calendarday.getYear()) {
// view.setText("今天");
view.setTextColor(COLOR_TX_THIS_MONTH_DAY);
// view.setBackgroundResource(R.drawable.bg_sign_today);
} else if (thisday.getMonth() == calendarday.getMonth()) {
view.setTextColor(COLOR_TX_THIS_MONTH_DAY);
} else {
view.setTextColor(COLOR_TX_OTHER_MONTH_DAY);
}
// 上面首先設定了一下預設的"當天"背景色,當有特殊需求時,才給當日填充背景色
// 設定日期背景色
if (dayBgColorMap.get(dates[i][j]) != null) {
// view.setTextColor(Color.WHITE);
view.setBackgroundResource(dayBgColorMap.get(dates[i][j]));
}
// 設定标記
setMarker(group, i, j);
day++;
// 下個月
} else {
if (calendarday.getMonth() == Calendar.DECEMBER) {
dates[i][j] =
format(new Date(calendarday.getYear() + 1, Calendar.JANUARY, nextMonthDay));
} else {
dates[i][j] =
format(new Date(calendarday.getYear(), calendarday.getMonth() + 1, nextMonthDay));
}
view.setText(Integer.toString(nextMonthDay));
view.setTextColor(COLOR_TX_OTHER_MONTH_DAY);
// 設定日期背景色
if (dayBgColorMap.get(dates[i][j]) != null) {
// view.setBackgroundResource(dayBgColorMap
// .get(dates[i][j]));
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
// 設定标記
setMarker(group, i, j);
nextMonthDay++;
}
}
}
}
}
thisday = new Date(); // 今天,calendarday; // 月曆這個月第一天(1号),有了這兩個概念,上面的代碼看起來就會一目了然,當然也可以在各種判斷語句中增加一些特定的需求,改成自己需要的樣式。接下來再簡單介紹一下setMarker方法:
// 設定标記
private void setMarker(RelativeLayout group, int i, int j) {
int childCount = group.getChildCount();
if (marksMap.get(dates[i][j]) != null) {
if (childCount < 2) {
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams((int) (tb * 0.7), (int) (tb * 0.7));
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
params.setMargins(0, 0, 1, 1);
ImageView markView = new ImageView(getContext());
markView.setImageResource(marksMap.get(dates[i][j]));
markView.setLayoutParams(params);
markView.setBackgroundResource(R.drawable.calendar_bg_tag);
group.addView(markView);
}
} else {
if (childCount > 1) {
group.removeView(group.getChildAt(1));
}
}
}
其中marksMap = new HashMap<String, Integer>(); // 儲存某個日子被标注的資源id,dates = new String[6][7]; 為目前月曆日期,group為包裝每個日子的布局;childCount = group.getChildCount(),為該布局的子view個數;首先從marksMap中查詢該日子是否需要标記,如果不需要,且該布局已經有兩個子view,即已經被設定了背景和标記,就移除标記;如果需要設定,且childCount小于2,就說明該點還未設定标記,添加标記布局。
至此幾個主要的方法已經介紹完畢,接下來就是如何使用了,其使用也是相當友善:
<com.test.signcalendar.SignCalendar
android:id="@+id/popupwindow_calendar"
android:layout_width="match_parent"
android:layout_height="320dp"
android:clickable="false" >
</com.test.signcalendar.SignCalendar>
在布局中聲明一下,然後再代碼中擷取SignCalendar的執行個體就可以進行相應的操作了,就不在此贅述,獻上demo代碼供大家學習交流。
點選擷取源碼值得學習的demo