0%

自定义日历选择器

前言

网上找的日历选择器一直不太满意,所以想着自己造个轮子

正文

造轮子,其一是定义数据源
so:void setData()
那么这个函数应该包含什么样的代码?让我们来分析下:
日历可以分为几个部分?

  1. 列标题
  2. 上个月的日期
  3. 本月日期
  4. 下个月日期

测量界面并定义几个数值:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//重新计算高度
measuredWidth = getMeasuredWidth();
if (measuredWidth != 0) {
textWidth = measuredWidth / 7f;//文本宽度
textHeight = textWidth * 1.1f;//文本高度
measuredHeight = (int) (textHeight * (lineSize + 1));//包括第一行 title
setMeasuredDimension(measuredWidth, measuredHeight);
}
}

列标题可以直接绘制,并非数据源,在onDraw(Canvas canvas)中定义titlePaint并绘制

1
2
3
4
5
if (titlePaint == null) {
titlePaint = new TextPaint();
titlePaint.setTextSize(textHeight / 3);
titlePaint.setColor(getResources().getColor(R.color.color_text_333333));
}

在绘制之前——我们定义了数据的字体大小和颜色——字的高度为文本框的三分之一,颜色为RGB: #333333

1
2
3
4
5
6
7
8
private void drawTitleText(Canvas canvas, float centerX, float centerY, String text) {
//文字度量
Paint.FontMetrics fontMetrics = titlePaint.getFontMetrics();
//得到基线的位置
float baselineY = centerY + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
float textWidth = titlePaint.measureText(text);//粗略计算文本宽度
canvas.drawText(text, centerX - textWidth / 2, baselineY, titlePaint);
}

设定数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public void setData(Calendar calBase) {
if (data == null) {
data = new ArrayList<>();
} else {
data.clear();
}

this.calBase = calBase;//当前页面的年和月的值


Calendar calStart = Calendar.getInstance();//本月开始 本月1号
calStart.set(calBase.get(Calendar.YEAR), calBase.get(Calendar.MONTH), 1);//本月1号

Calendar calEnd = Calendar.getInstance();//本月结束 本月最后一天
calEnd.set(calBase.get(Calendar.YEAR), calBase.get(Calendar.MONTH) + 1, 1);//下个月1号
calEnd.add(Calendar.DATE, -1);//本月最后一天

//周日 为 1 计算需要展示的上个月日期数
int dayOfWeek = calStart.get(Calendar.DAY_OF_WEEK);
int lastNum = dayOfWeek - 1;
for (int i = 0; i < lastNum; i++) {//添加上个月数据
Calendar calendar = Calendar.getInstance();
calendar.set(calBase.get(Calendar.YEAR), calBase.get(Calendar.MONTH), 1);
calendar.add(Calendar.DATE, -lastNum + i);
DateEntity dateEntity = new DateEntity();//日期基本类型
dateEntity.setCalendar(calendar);
dateEntity.setLastMonth(true);//是否为上个月
data.add(dateEntity);
Log.i(TAG, LunarCalendarUtil.getLunarDate(calendar));
}

for (int i = 0; i < calEnd.get(Calendar.DAY_OF_MONTH); i++) {//添加本月数据
Calendar calendar = Calendar.getInstance();
calendar.set(calBase.get(Calendar.YEAR), calBase.get(Calendar.MONTH), 1);
calendar.add(Calendar.DATE, i);
DateEntity dateEntity = new DateEntity();
dateEntity.setCalendar(calendar);
data.add(dateEntity);
Log.i(TAG, LunarCalendarUtil.getLunarDate(calendar));
}

int residueSize = 7 - data.size() % 7;
if (residueSize != 7) {
for (int i = 0; i < residueSize; i++) {//添加下个月数据
Calendar calendar = Calendar.getInstance();
calendar.set(calBase.get(Calendar.YEAR), calBase.get(Calendar.MONTH) + 1, 1);
calendar.add(Calendar.DATE, i);
DateEntity dateEntity = new DateEntity();
dateEntity.setCalendar(calendar);
dateEntity.setNextMonth(true);//是否为下个月
data.add(dateEntity);
Log.i(TAG, LunarCalendarUtil.getLunarDate(calendar));
}
}

lineSize = data.size() / 7;

requestLayout();//这里不用invalidate();因为要重新测量界面
}

绘制数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void drawData(Canvas canvas) {
for (int i = 0; i < data.size(); i++) {
DateEntity dateEntity = data.get(i);
int line = i / 7 + 2;//第二行开始
float baseX = textWidth / 2f + textWidth * (float) (i % 7);//文字中心在X轴的位置
float baseY = textHeight / 2f + textHeight * (line - 1);//文字中心在Y轴的位置
float numberY = baseY - textHeight / 6f;//上移6分之一文字高度,给农历腾空间
drawText(canvas, baseX, numberY, dateEntity.getText(), numberPaint, dateEntity.isHint(), dateEntity.isSelected());//阳历

float lunarY = baseY + textHeight / 6f;
drawText(canvas, baseX, lunarY, dateEntity.getLunarText(), lunarPaint, dateEntity.isHint(), dateEntity.isSelected());//农历
}
}

private void drawText(Canvas canvas, float centerX, float centerY, String text, TextPaint textPaint, boolean isHint, boolean isSelected) {
if (isHint) {
textPaint.setColor(hintColor);
} else {
textPaint.setColor(textColor);
}

//文字度量
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
//得到基线的位置
float baselineY = centerY + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
float textWidth = textPaint.measureText(text);//粗略计算文本宽度
canvas.drawText(text, centerX - textWidth / 2, baselineY, textPaint);
}

效果图.png

点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_UP:
float upX = event.getX();
float upY = event.getY();
if (Math.abs(upX - downX) < minMove && Math.abs(upY - downY) < minMove) {
int column = (int) (upX / textWidth);
int line = (int) (upY / textHeight);
Log.i(TAG, "列:" + column + "-" + "行:" + line);
int i = (line - 1) * 7 + column;//第一行是标题 计算点击的是到底几个data
if (i >= 0) {
if (data.get(i).isHint()) {//如果是上个月,则返回上个月 反之
if (data.get(i).isLastMonth()) {
lastMonth();
} else {
nextMonth();
}
if (onMonthChangeListener != null)
onMonthChangeListener.onChanged(calBase);
} else {
// clearSelected();
if (selectedType == TYPE_RANGE) {//如果是范围选择
if (calSelectedEnd != null) {//选中最后一项,则重新开始选择
clearSelected();
}
if (calSelectedStart == null) {
setSelectedStart(data.get(i).getCalendar());
} else {
setSelectedEnd(data.get(i).getCalendar());
}
} else if (selectedType == TYPE_SINGLE) {//如果是单选
// clearSelected();
setSelected(data.get(i).getCalendar());
}
if (onDayClickListener != null)
onDayClickListener.onClickListener(data.get(i));
}
}
}
break;
}
return event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP;
}

记录完选中区,应该重新绘制界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (selectedType == TYPE_RANGE) {
if (calSelectedStart != null) {
if (calSelectedEnd == null) {
dateEntity.setSelected(calSelectedStart.getTimeInMillis() == dateEntity.getCalendar().getTimeInMillis());
} else {
dateEntity.setSelected(!dateEntity.getCalendar().before(calSelectedStart) && !dateEntity.getCalendar().after(calSelectedEnd));
}
} else {
dateEntity.setSelected(false);
}
} else if (selectedType == TYPE_SINGLE) {
if (calSelected != null) {
dateEntity.setSelected(calSelected.getTimeInMillis() == dateEntity.getCalendar().getTimeInMillis());
} else
dateEntity.setSelected(false);
}

if (dateEntity.isSelected()) {
canvas.drawLine(baseX - textWidth / 2f, baseY, baseX + textWidth / 2f, baseY, selectedPaint);
}

最终呈现:

最终预览

引用:

1
2
3
4
5
6
7
8
9
10
11
<com.yooking.tools.calendarview.CalendarPickerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"

app:hintColor="@color/color_text_999999"
app:maskColor="@color/color_text_333333"
app:selectedColor="#0000ff"
app:selectedTextColor="@color/white"
app:textColor="@color/color_text_333333"
app:selectedType="range"/>

结语

源码:源码-Github

Demo:Demo-Github

------------本文结束感谢您的阅读------------

Thank you for your accept!