Lemon-XQ

Android项目开发笔记之Material日历模块实现(支持滑动/直接选择年月)

写在前面

  本系列第二篇(隔了好久的感觉orz)。本篇主要讲的是很多App里都会有的日历模块实现,基于Github上一个优秀的开源项目进行了扩展,支持滑动切换月份以及下拉直接选择年月~

实现效果

先放一下实现的效果图:

引入插件

  • 在app/build.gradle的dependencies中增加:
    compile 'com.haibin:calendarview:3.2.0'
  • 重新sync

定义布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<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,代码如下:
    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
    60
    61
    62
    63
    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是中文的,个人觉得英文的看起来比较舒服,所以自定义了一个,步骤如下:

  1. 新建english_weekbar.xml,内容是7个TextView,代表周一到周日,代码比较简单就不贴了
  2. 新建EnglishWeekbar.java,加载english_weekbar.xml布局,代码如下:
    1
    2
    3
    4
    5
    6
    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中使用:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <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年)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 滑动日历时更改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事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 年月选择监听,提取点击项的年份及月份,滑动日历至目标月份
    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);
    }
    });

其他

  1. 使用calendarView.setSchemeDate(dateList)可以设置特殊日期
  2. com.haibin.calendarview中也有一个Calendar类,不要和java.util.Calendar混了,特别在进行java.util.Date和calendar的转换时需要注意。
Lemon-XQ wechat
坚持原创技术分享,您的支持将鼓励我继续创作!