customcalendar.png
最近項目有個日歷列表的需求,樣式如上所示,之前考慮過用MaterialCalendarView,但是滑動的時候不刷新年月title,且MaterialCalendarView使用ViewPager等嵌套完成的,這樣使用性能不太優秀,且比較卡頓所以自定義了一個日歷控件
一.自定義日歷控件
**
* 作者:徐敏敏 on 2016/12/25 0025 17:47
* 郵箱:15067596185@163.com
* 自定義日歷控件
*/
public class CalendarScheduleView extends View {
// 畫筆
private Paint paint;
// 列數
private static final int NUMS_COLUMN = 7;
// 行數(星期一行加日期六行)
private static final int NUMS_ROW = 7;
// 周日到周六的顏色
private int mWeekColor = Color.parseColor("#8B8B8B");
// 本月日期的顏色
private int mMonthDateColor = Color.parseColor("#000000");
// 可選日期的背景顏色
private int mSelectableDayColor = Color.parseColor("#00bcbe");
// 星期字體大小
private int mWeekSize = 10;
// 日期字體大小
private int mDateSize = 10;
// 可選、選擇日期的圓圈半徑
private float mCircleR;
//當前Date
private Date date;
// 當前年
private int mCurrentYear;
// 當前月
private int mCurrentMonth;
// 已記錄日期
private List<Date> mSelectableDates = new ArrayList<>();
// 7行7列(第一行沒有數據,為了計算位置方便,將星期那一行考慮進去)
private int[][] days = new int[NUMS_ROW][NUMS_COLUMN];
// 列寬
private int mColumnWidth;
// 行高
private int mRowHeight;
// DisplayMetrics對象
private DisplayMetrics displayMetrics;
/**
* 構造函數
*
* @param context
* @param attrs
* @description 初始化
*/
public CalendarScheduleView(Context context, AttributeSet attrs) {
super(context, attrs);
// 獲取DisplayMetrics實例
displayMetrics = getResources().getDisplayMetrics();
// 獲取日歷實例
Calendar calendar = Calendar.getInstance();
// new一個Paint實例(抗鋸齒)
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 獲取當前年份
mCurrentYear = calendar.get(Calendar.YEAR);
// 獲取當前月份
mCurrentMonth = calendar.get(Calendar.MONTH);
this.date = new Date();
}
public void setCurrentDate(Date date) {
mCurrentYear = Integer.parseInt(TimeUtils.date2String(date, "yyyy"));
mCurrentMonth = Integer.parseInt(TimeUtils.date2String(date, "MM"));
this.date = date;
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST) {
widthSize = displayMetrics.densityDpi * 100;
}
int heightSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(widthMeasureSpec);
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = displayMetrics.densityDpi * 120;
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onDraw(Canvas canvas) {
// 計算每一列寬度
mColumnWidth = getWidth() / NUMS_COLUMN;
// 計算每一行高度
mRowHeight = getHeight() / NUMS_ROW;
// 繪制星期
drawDayOfWeekText(canvas);
// 繪制本月日期
drawDateText(canvas);
}
/**
* 繪制星期
*
* @param canvas
*/
private void drawDayOfWeekText(Canvas canvas) {
for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
// 星期在第0行
int row = 0;
// 列數
int column = dayOfWeek;
// 填寫星期
String day[] = {"日", "一", "二", "三", "四", "五", "六"};
// 設置字體大小
paint.setTextSize(mWeekSize * displayMetrics.scaledDensity);
// 設置畫筆顏色
paint.setColor(mWeekColor);
// 左邊坐標(居中顯示)
int left = (int) (mColumnWidth * column + (mColumnWidth - paint.getTextSize()) / 2);
// 頂部坐標 (注意,豎直方向上是以baseline為基準寫字的,因此要 - (paint.ascent() + paint.descent()) / 2)
int top = (int) (mRowHeight * row + mRowHeight / 2 - (paint.ascent() + paint.descent()) / 2);
// 繪制文字
canvas.drawText(day[dayOfWeek] + "", left, top, paint);
}
}
/**
* 繪制本月日期(若有背景,需要先繪制背景,否則會覆蓋文字)
*
* @param canvas
*/
private void drawDateText(Canvas canvas) {
// 獲取日歷實例
Calendar calendar = Calendar.getInstance();
calendar.setTime(this.date);
// 獲取當前月份天數
int daysOfMonth = calendar.getActualMaximum(Calendar.DATE);
// 獲取當月第一天是一周中的第幾天(周日為第一天)
int firstDayOfWeekInMonth = TimeUtils.getWeekIndex(TimeUtils.string2Date(String.valueOf(mCurrentYear) + "-" + String.valueOf(mCurrentMonth) + "-01", "yyyy-MM-dd"));
// 寫入文字
for (int date = 1; date <= daysOfMonth; date++) {
// 當前日期所在行數(第一行為1:由于第一行顯示星期,故日期行數加一)
int row = (date + firstDayOfWeekInMonth - 1 - 1) / 7 + 1;
// 當前日期所在列數(第一列為0)
int column = (date + firstDayOfWeekInMonth - 1 - 1) % 7;
// 儲存日期信息
days[row][column] = date;
// 若是可選日期,繪制背景
for (Date day : mSelectableDates) {
calendar.setTime(day);
int i = calendar.get(Calendar.DAY_OF_MONTH);
if (i == date) {
drawSelectableBackground(canvas, row, column);
}
}
// 設置字體大小
paint.setTextSize(mDateSize * displayMetrics.scaledDensity);
// 設置畫筆顏色
paint.setColor(mMonthDateColor);
// 日期左邊坐標(居中顯示)
int left = (int) (mColumnWidth * column + (mColumnWidth - paint.measureText(date + "")) / 2);
// 日期頂部坐標 (注意,豎直方向上是以baseline為基準寫字的,因此要 - (paint.ascent() + paint.descent()) / 2)
int top = (int) (mRowHeight * row + mRowHeight / 2 - (paint.ascent() + paint.descent()) / 2);
// 繪制文字
canvas.drawText(date + "", left, top, paint);
}
}
/**
* 繪制可選日期背景
*
* @param canvas
* @param row
* @param column
*/
private void drawSelectableBackground(Canvas canvas, int row, int column) {
Paint paint = new Paint();
// 畫筆顏色
paint.setColor(mSelectableDayColor);
paint.setAntiAlias(true); //消除鋸齒
paint.setStyle(Paint.Style.STROKE);//繪制空心圓或 空心矩形
paint.setStrokeWidth(2);
// 圓心位置
float cX = (float) (mColumnWidth * column + mColumnWidth / 2);
float cY = (float) (mRowHeight * row + mRowHeight / 2);
// 圓形半徑
mCircleR = (float) (mColumnWidth / 2 * 0.8);
// 繪制圓形背景
canvas.drawCircle(cX, cY, mCircleR, paint);
}
public int getmWeekColor() {
return mWeekColor;
}
public void setmWeekColor(int mWeekColor) {
this.mWeekColor = mWeekColor;
}
public int getmMonthDateColor() {
return mMonthDateColor;
}
public void setmMonthDateColor(int mMonthDateColor) {
this.mMonthDateColor = mMonthDateColor;
}
public int getmSelectableDayColor() {
return mSelectableDayColor;
}
public void setmSelectableDayColor(int mSelectableDayColor) {
this.mSelectableDayColor = mSelectableDayColor;
}
public int getmWeekSize() {
return mWeekSize;
}
public void setmWeekSize(int mWeekSize) {
this.mWeekSize = mWeekSize;
}
public int getmDateSize() {
return mDateSize;
}
public void setmDateSize(int mDateSize) {
this.mDateSize = mDateSize;
}
public float getmCircleR() {
return mCircleR;
}
public void setmCircleR(float mCircleR) {
this.mCircleR = mCircleR;
}
public List<Date> getmSelectableDates() {
return mSelectableDates;
}
public void setmSelectableDates(List<Date> mSelectableDates) {
this.mSelectableDates.clear();
this.mSelectableDates = mSelectableDates;
}
}
二.之前在滑動中還是會遇到卡頓,發現是繪制特殊日期圓環造成的,檢查了很久代碼,才校驗SimpleDateFormat轉化比Calendar轉換效率低得多,當然幾次轉換是察覺不到卡頓的,但假如循環多次,肉眼就會察覺到明顯的卡頓,即使設置滑動不加載數據,停止時還是需要好幾秒才繪制成功。
1.轉化2017-11-11這種類型的日期文本使用SimpleDateFormat
2.獲取年或月或日還是用Calendar較好