写在前面
本系列第二篇(隔了好久的感觉orz)。本篇主要讲的是很多App里都会有的日历模块实现,基于Github上一个优秀的开源项目进行了扩展,支持滑动切换月份以及下拉直接选择年月~
实现效果
先放一下实现的效果图:
引入插件
- 在app/build.gradle的dependencies中增加:
compile 'com.haibin:calendarview:3.2.0'
- 重新sync
定义布局
<com.haibin.calendarview.CalendarView
android:id="@+id/history_calendarView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="5"
android:background="@color/colorPrimary"
app:month_view="你的包名.CustomMonthView"
app:week_bar_view="你的包名.EnglishWeekbar"
app:week_line_background="@color/white"
app:calendar_height="37dp"
app:current_day_text_color="#d94373"
app:selected_theme_color="@color/white"
app:lunar_text_size="0sp"
app:min_year="2018"
app:max_year="2050"
app:month_view_show_mode="mode_all"
app:other_month_text_color="#82ffffff"
/>
自定义MonthView(选中日期时效果 & 特殊事件效果)
- 新建CustomMonthView.java,代码如下:
public class CustomMonthView extends MonthView {
private int mRadius;
public CustomMonthView(Context context) {
super(context);
mSchemePaint.setColor(Color.WHITE);
mSchemeTextPaint.setColor(Color.GREEN);
}
@Override
protected void onPreviewHook() {
mRadius = Math.min(mItemWidth, mItemHeight) / 5 * 2;
mCurMonthTextPaint.setColor(Color.WHITE);// 白色圆圈
mSchemePaint.setStyle(Paint.Style.FILL);
mSelectTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
setLayerType( LAYER_TYPE_SOFTWARE , null);
}
@Override
protected void onLoopStart(int x, int y) {
}
@Override
protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme) {
// 画白色圆圈
int cx = x + mItemWidth / 2;
int cy = y + mItemHeight / 2;
canvas.drawCircle(cx, cy, mRadius, mSelectedPaint);
return false;
}
@Override
protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y) {
// 画右上角白点,表示特殊日期
int cx = x + 4*mItemWidth / 5;
int cy = y + mItemHeight / 5;
canvas.drawCircle(cx, cy, mRadius/4, mSchemePaint);
}
@Override
protected void onDrawText(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme, boolean isSelected) {
float baselineY = mTextBaseLine + y;
int cx = x + mItemWidth / 2;
if (isSelected) {
canvas.drawText(String.valueOf(calendar.getDay()),
cx,
baselineY,
mSelectTextPaint);
}else if (hasScheme) {
canvas.drawText(String.valueOf(calendar.getDay()),
cx,
baselineY,
calendar.isCurrentDay() ? mCurDayTextPaint :
calendar.isCurrentMonth() ? mSchemeTextPaint : mOtherMonthTextPaint);
} else {
canvas.drawText(String.valueOf(calendar.getDay()), cx, baselineY,
calendar.isCurrentDay() ? mCurDayTextPaint :
calendar.isCurrentMonth() ? mCurMonthTextPaint : mOtherMonthTextPaint);
}
}
}
自定义WeekBar(英文版)
默认Weekbar是中文的,个人觉得英文的看起来比较舒服,所以自定义了一个,步骤如下:
- 新建english_weekbar.xml,内容是7个TextView,代表周一到周日,代码比较简单就不贴了
- 新建EnglishWeekbar.java,加载english_weekbar.xml布局,代码如下:
public class EnglishWeekbar extends WeekBar {
public EnglishWeekbar(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.english_week_bar, this, true);
}
}
制作下拉选择控件(使用Spinner)
同样介绍一个github上的项目,可定制项比较丰富,且有Material效果。
- 引入:
implementation 'com.jaredrummler:material-spinner:1.2.4@aar'
- xml中使用:
<com.jaredrummler.materialspinner.MaterialSpinner
android:id="@+id/dateSpinner"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginStart="85dp"
app:ms_background_color="@color/colorPrimary"
app:ms_background_selector="@color/colorPrimary"
app:ms_arrow_tint="@color/white"
app:ms_dropdown_height="250dp"
app:ms_hint_color="@color/black"
app:ms_text_color="@color/white"
android:textSize="18sp" />
- activity中往Spinner填充日期数据(范围为2018年-2050年)
private int currentIndex;
void initData(){
currentIndex = 1;
int cur_year = Util.getYear(new Date());
int cur_month = Util.getMonth(new Date());
List<String> dateList = new ArrayList<>();
for(int year=2018; year<=2050; year++){
for(int month=1;month<=12;month++){
dateList.add(year+"-"+month);
if(year <= cur_year && month < cur_month) currentIndex++;
}
}
dateSpinner.setItems(dateList);// 填充数据
dateSpinner.setSelectedIndex(currentIndex);// 默认选中当前月
}
控件组合
不难发现,上面定义的这两个控件其实是没有关系的,那么怎么实现滑动切换的时候,Spinner显示的日期也会改变,而下拉选择某月后,CalendarView又会滑动到目标月份呢?其实也不难,只需要监听二者相应的事件,再进行处理即可,具体步骤如下:
1. 监听calendarView的onMonthChanged事件
// 滑动日历时更改Title显示,根据与当前月份的偏移量计算目标月份在Spinner中的索引值
calendarView.setOnMonthChangeListener(new CalendarView.OnMonthChangeListener() {
@Override
public void onMonthChange(int year, int month) {
int cur_month = calendarView.getCurMonth();
int cur_year = calendarView.getCurYear();
int offset;
if(cur_year == year){
offset = month - cur_month;
}else{
offset = (year - cur_year)*12 + month - cur_month;
}
dateSpinner.setSelectedIndex(currentIndex + offset);
}
});
2.监听dateSpinner的onItemSelected事件
// 年月选择监听,提取点击项的年份及月份,滑动日历至目标月份
dateSpinner.setOnItemSelectedListener(new MaterialSpinner.OnItemSelectedListener<String>() {
@Override
public void onItemSelected(MaterialSpinner view, int position, long id, String item) {
String[] dateStr = item.split("-");
int year = Integer.parseInt(dateStr[0]);
int month = Integer.parseInt(dateStr[1]);
calendarView.scrollToCalendar(year,month,1);
}
});
其他
- 使用calendarView.setSchemeDate(dateList)可以设置特殊日期
- com.haibin.calendarview中也有一个Calendar类,不要和java.util.Calendar混了,特别在进行java.util.Date和calendar的转换时需要注意。