美文网首页功能控件自定义控件开源库
[83→100]Android好用的日历控件——Material

[83→100]Android好用的日历控件——Material

作者: 沉思的Panda | 来源:发表于2016-07-19 17:26 被阅读16749次

    需求

    最近项目中需要用到日历控件,需求如下

    1. 同时支持周模式和月模式
    2. 支持边界设置,有些地方要求只显示当前周和下一周
    3. 支持日期禁用,某些特定的日期不允许点击
    4. 支持自定义的选中日期的样式
    5. 左右滑动自动切换周、月,同时自动选中同等位置的日期。

    找来找去,最后发现GitHub的material-calendarview这个项目最贴近以上需求,稍作修改就能用了。

    material-calendarview

    项目开源地址:
    https://github.com/prolificinteractive/material-calendarview

    集成清单

    1. 添加compile 'com.prolificinteractive:material-calendarview:1.4.0'
    2. 添加日历控件到布局中
    <com.prolificinteractive.materialcalendarview.MaterialCalendarView
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/calendarView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:mcv_showOtherDates="all"
        app:mcv_selectionColor="#00F"
        />
    

    功能展示

    material-calendarview项目提供9个Sample来展示其功能用法

    一、对照组:OldCalendarViewActivity

    <CalendarView
            android:id="@+id/calendarView"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            />
    

    采用系统控件CalendarView,实现一个点击显示日期的功能,可惜在低版本Android手机上没有material的样式,颜值很低,不能用。

    二、基本用法:BasicActivity

    1. 绑定控件
    @Bind(R.id.calendarView)
    MaterialCalendarView widget;
    
    1. OnDateSelectedListener:用来监听选中日期的变化,
    /**
     * The callback used to indicate a date has been selected or deselected
     */
    public interface OnDateSelectedListener {
    
        /**
         * Called when a user clicks on a day.
         * There is no logic to prevent multiple calls for the same date and state.
         *
         * @param widget   the view associated with this listener
         * @param date     the date that was selected or unselected
         * @param selected true if the day is now selected, false otherwise
         */
        void onDateSelected(@NonNull MaterialCalendarView widget, @NonNull CalendarDay date, boolean selected);
    }
    
    1. OnMonthChangedListener:用来接听页面滑动的变化,这个有点名不副实,因为在周模式的时候,滑动页面是按周来变化的
    /**
     * The callback used to indicate the user changes the displayed month
     */
    public interface OnMonthChangedListener {
    
        /**
         * Called upon change of the selected day
         *
         * @param widget the view associated with this listener
         * @param date   the month picked, as the first day of the month
         */
        void onMonthChanged(MaterialCalendarView widget, CalendarDay date);
    }
    
    1. getSelectedDate():获取被选中的日期。
    private String getSelectedDatesString() {
        CalendarDay date = widget.getSelectedDate();
        if (date == null) {
            return "No Selection";
        }
        return FORMATTER.format(date.getDate());
    }
    

    三、修饰选中日期:BasicActivityDecorated

    1. 设置日期范围
    widget.setShowOtherDates(MaterialCalendarView.SHOW_ALL);
    Calendar instance = Calendar.getInstance();
    widget.setSelectedDate(instance.getTime());
    Calendar instance1 = Calendar.getInstance();
    instance1.set(instance1.get(Calendar.YEAR), Calendar.JANUARY, 1);
    Calendar instance2 = Calendar.getInstance();
    instance2.set(instance2.get(Calendar.YEAR), Calendar.DECEMBER, 31);
    widget.state().edit()
            .setMinimumDate(instance1.getTime())
            .setMaximumDate(instance2.getTime())
            .commit();
    
    1. 增加日期修饰
    widget.addDecorators(
            new MySelectorDecorator(this),
            new HighlightWeekendsDecorator(),
            oneDayDecorator
    );
    

    案例提供了三种修饰:

    1. 对选中日期增加指定背景图层
    public class MySelectorDecorator implements DayViewDecorator {
    
        private final Drawable drawable;
    
        public MySelectorDecorator(Activity context) {
            drawable = context.getResources().getDrawable(R.drawable.my_selector);
        }
    
        @Override
        public boolean shouldDecorate(CalendarDay day) {
            return true;
        }
    
        @Override
        public void decorate(DayViewFacade view) {
            view.setSelectionDrawable(drawable);
        }
    }
    
    1. 对周末增加指定背景图层
    /**
     * Highlight Saturdays and Sundays with a background
     */
    public class HighlightWeekendsDecorator implements DayViewDecorator {
    
        private final Calendar calendar = Calendar.getInstance();
        private final Drawable highlightDrawable;
        private static final int color = Color.parseColor("#228BC34A");
    
        public HighlightWeekendsDecorator() {
            highlightDrawable = new ColorDrawable(color);
        }
    
        @Override
        public boolean shouldDecorate(CalendarDay day) {
            day.copyTo(calendar);
            int weekDay = calendar.get(Calendar.DAY_OF_WEEK);
            return weekDay == Calendar.SATURDAY || weekDay == Calendar.SUNDAY;
        }
    
        @Override
        public void decorate(DayViewFacade view) {
            view.setBackgroundDrawable(highlightDrawable);
        }
    }
    
    1. 对选中日期增加指定修饰
    /**
     * Decorate a day by making the text big and bold
     */
    public class OneDayDecorator implements DayViewDecorator {
    
        private CalendarDay date;
    
        public OneDayDecorator() {
            date = CalendarDay.today();
        }
    
        @Override
        public boolean shouldDecorate(CalendarDay day) {
            return date != null && day.equals(date);
        }
    
        @Override
        public void decorate(DayViewFacade view) {
            view.addSpan(new StyleSpan(Typeface.BOLD));
            view.addSpan(new RelativeSizeSpan(3.0f));
        }
    
        /**
         * We're changing the internals, so make sure to call {@linkplain MaterialCalendarView#invalidateDecorators()}
         */
        public void setDate(Date date) {
            this.date = CalendarDay.from(date);
        }
    }
    
    1. 对特定日期增加红点修饰
    /**
     * Decorate several days with a dot
     */
    public class EventDecorator implements DayViewDecorator {
    
        private int color;
        private HashSet<CalendarDay> dates;
    
        public EventDecorator(int color, Collection<CalendarDay> dates) {
            this.color = color;
            this.dates = new HashSet<>(dates);
        }
    
        @Override
        public boolean shouldDecorate(CalendarDay day) {
            return dates.contains(day);
        }
    
        @Override
        public void decorate(DayViewFacade view) {
            view.addSpan(new DotSpan(5, color));
        }
    }
    

    四、模式切换:SwappableBasicActivityDecorated

    切换周模式、月模式

    @OnClick(R.id.button_weeks)
    public void onSetWeekMode() {
        widget.state().edit()
                .setCalendarDisplayMode(CalendarMode.WEEKS)
                .commit();
    }
    
    @OnClick(R.id.button_months)
    public void onSetMonthMode() {
        widget.state().edit()
                .setCalendarDisplayMode(CalendarMode.MONTHS)
                .commit();
    }
    

    五、日期禁用:DisableDaysActivity

    核心代码是:

    DayViewFacade view
    view.setDaysDisabled(true);
    

    这个作者非常喜欢用装饰模式。所以对DayViewFacade的修饰可以无穷的累加。只要集成DayViewDecorator就可以。

    /**
     * Decorate Day views with drawables and text manipulation
     */
    public interface DayViewDecorator {
    
        /**
         * Determine if a specific day should be decorated
         *
         * @param day {@linkplain CalendarDay} to possibly decorate
         * @return true if this decorator should be applied to the provided day
         */
        boolean shouldDecorate(CalendarDay day);
    
        /**
         * Set decoration options onto a facade to be applied to all relevant days
         *
         * @param view View to decorate
         */
        void decorate(DayViewFacade view);
    
    }
    

    六、透过xml自定义控件:CustomizeXmlActivity

    <com.prolificinteractive.materialcalendarview.MaterialCalendarView
            android:id="@+id/calendarView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:mcv_showOtherDates="all"
            app:mcv_arrowColor="?attr/colorPrimary"
            app:mcv_leftArrowMask="@drawable/ic_navigation_arrow_back"
            app:mcv_rightArrowMask="@drawable/ic_navigation_arrow_forward"
            app:mcv_selectionColor="?attr/colorPrimary"
            app:mcv_headerTextAppearance="?android:attr/textAppearanceMedium"
            app:mcv_dateTextAppearance="@style/CustomDayTextAppearance"
            app:mcv_weekDayTextAppearance="?android:attr/textAppearanceMedium"
            app:mcv_weekDayLabels="@array/custom_weekdays"
            app:mcv_monthLabels="@array/custom_months"
            app:mcv_tileSize="36dp"
            app:mcv_firstDayOfWeek="thursday"
            app:mcv_calendarMode="week"
            />
    

    xml中能设置的属性很多,常用的如下:

    • mcv_showOtherDates:日期范围
    • mcv_firstDayOfWeek:一周的第一天
    • mcv_calendarMode:日历模式

    七、透过java自定义控件:CustomizeCodeActivity

    效果和xml是一致的。

    widget.setShowOtherDates(MaterialCalendarView.SHOW_ALL);
    widget.setArrowColor(getResources().getColor(R.color.sample_primary));
    widget.setLeftArrowMask(getResources().getDrawable(R.drawable.ic_navigation_arrow_back));
    widget.setRightArrowMask(getResources().getDrawable(R.drawable.ic_navigation_arrow_forward));
    widget.setSelectionColor(getResources().getColor(R.color.sample_primary));
    widget.setHeaderTextAppearance(R.style.TextAppearance_AppCompat_Medium);
    widget.setWeekDayTextAppearance(R.style.TextAppearance_AppCompat_Medium);
    widget.setDateTextAppearance(R.style.CustomDayTextAppearance);
    widget.setTitleFormatter(new MonthArrayTitleFormatter(getResources().getTextArray(R.array.custom_months)));
    widget.setWeekDayFormatter(new ArrayWeekDayFormatter(getResources().getTextArray(R.array.custom_weekdays)));
    widget.setTileSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 36, getResources().getDisplayMetrics()));
    
    CalendarDay today = CalendarDay.from(2016, 5, 2);
    widget.setCurrentDate(today);
    widget.setSelectedDate(today);
    
    widget.state().edit()
            .setFirstDayOfWeek(Calendar.WEDNESDAY)
            .setMinimumDate(CalendarDay.from(2016, 4, 3))
            .setMaximumDate(CalendarDay.from(2016, 5, 12))
            .setCalendarDisplayMode(CalendarMode.WEEKS)
            .commit();
    

    八、完整的设置展示DynamicSettersActivity

    九、在Dialog中显示日历:DialogsActivity

    拓展:滑屏时自动选中日期

    widget.setOnDateChangedListener(new OnDateSelectedListener() {
        @Override
        public void onDateSelected(@NonNull MaterialCalendarView widget, @NonNull CalendarDay date, boolean selected) {
            if (selected && date != null){
                dayOfWeek = date.getCalendar().get(Calendar.DAY_OF_WEEK);
                dayofMonth = date.getCalendar().get(Calendar.DAY_OF_MONTH);
            }
            Log.d(TAG, "date = " + date);
            Log.d(TAG, "dayOfWeek = " + dayOfWeek);
            Log.d(TAG, "dayofMonth = " + dayofMonth);
        }
    });
    
    widget.setOnMonthChangedListener(new OnMonthChangedListener() {
        @Override
        public void onMonthChanged(final MaterialCalendarView widget, CalendarDay date) {
            Calendar c = date.getCalendar();
            Log.d(TAG, "date = " + date);
            Calendar now = c;
            if (widget.state().calendarMode == CalendarMode.WEEKS){
                now.set(Calendar.DAY_OF_WEEK, c.get(Calendar.DAY_OF_WEEK) + dayOfWeek - 1);
            }
            else{
                now.set(Calendar.DAY_OF_MONTH, c.get(Calendar.DAY_OF_MONTH) + dayofMonth - 1);
            }
            CalendarDay nowDate = CalendarDay.from(now);
            Log.d(TAG, "nowDate = " + nowDate);
            widget.setSelectedDate(nowDate);
        }
    });
    
    Calendar instance = Calendar.getInstance();
    widget.setSelectedDate(instance.getTime());
    

    Panda
    2016-07-19

    相关文章

      网友评论

      • 2de49878ced2:您好,请问我这样设置后
        Calendar calendar = Calendar.getInstance();
        binding.calendarView.setTileHeightDp(30);
        binding.calendarView.setSelectedDate(calendar.getTime());
        为什么dayView的checkedDrawable会先是扁的然后再是正常的圆的,切换很快,您有遇到过这个问题吗?
      • 建木晷天:有个问题想请教一下,我在给日期添加下标时,不显示,滑动后才显示。而且显示的是前一个月的下标标记。
        建木晷天:@ButterKnife 没有,换其他控件了
        ButterKnife:解决了吗?
      • 82c5de5512c8:请问已经添加的decorator如何删除呢
        海港少年与猫:@SeniorWilliam removedecorates
      • 663a2bcb5ce4:请问楼主怎样刷新选中日期状态呢?
      • 竹笙微凉:点击日期,更换背景图片,会出现混乱。会先呈现上一次的背景图片,然后再变成这一次的背景图片。
      • 磊_ab93:你好,有办法在周模式的时候,不让星期跟随移动吗?或者有办法在周模式下隐藏星期吗?
      • d8ceff4eefc6:怎么设置 禁止选择今天之前的所有日期 ?
      • a3f4fdbfbb5e:我看这个控件有多选模式 多选的话怎么获取所有选中的日期呢
      • 辩护人:感谢楼主发现的这个控件
      • 溘溘绊绊的一辈子:怎么把英文月份改成中文呢
      • 4692a2d268c4:作者你好,方便的话麻烦加我一下QQ,944743647,想请教一些问题,谢谢
      • 七岁就狠拽:这个怎么选择年份么?
      • haisiZ:你好,我现在需要在android studio直接将material_calendar_view这个库导入我工程,可是一直不成功,能教教我怎么导吗,研究了好久,刚刚学的android,我用的是android studio2.2.2,API16,android4.1,support.v7;好像material_calendar_view用的是support.v4,各种错误。
        DaydreamC:API16是targetAPI吗?如果是就太低了, 不支持的
      • 雇个城管打天下:请问楼主,如何让这个日历控件一打开就默认显示当前的日期被选中呢?
        b07643cf751e:同问,请问你解决了吗
      • 3daf11757c04:请问楼主怎么修改特定日期字体的颜色 还有特定日期的选定样式 EventDecorator 这个类只能修改字体那一丁点地方的样式 很难起到醒目的效果
        Dragon_M:请问你用这个实现了标记特定日期吗
      • wallace_5f82:demo apk能发下吗,github的项目我编译不通过。
      • 孟威:这个项目能在Eclipse里使用吗?
        沉思的Panda:@孟威 在Eclipse只能把源码都复制进去了
      • 李亦然:请问楼主,有办法做到周和月的平滑切换吗?
        Thebloodelves:@李亦然 平滑过渡,做出来了吗?我现在也需要
        李亦然:@沉思的Panda 目前做一个 TODO 项目,用到了周和月切换时平滑过渡。期待楼主的实现。
        沉思的Panda:@李亦然 这个目前没做,以后做了的话,我告诉你啊
      • 再见安妮:请问楼主这个日期空间可以在上面设置值吗
        沉思的Panda:@再见安妮 可以,利用日期修饰模式可以自定义view的内容
      • cb2794101ef2:非常有用,确实好看些,感谢你的分享
        沉思的Panda:@Mulqueen 有用就好~
      • 一只阿拉斯加的虎:这个真是好东西呀 作者怎么想出来的 比之前网上的找的好多了 :+1:
        一只阿拉斯加的虎:辣也很厉害 哈哈 。。分享的精神 :relaxed:
        沉思的Panda:@64229f9db00c 这个不是我写的,是我在github找到了,github做的太优秀了,所以能聚拢一流的程序员在上面写作品
      • Euterpe:demo 有上传到github么?
        Euterpe: @沉思的Panda 恩好的。因为手机上看 没点链接进去
        沉思的Panda:@Euterpe 有啊,就在 https://github.com/prolificinteractive/material-calendarview 里面

      本文标题:[83→100]Android好用的日历控件——Material

      本文链接:https://www.haomeiwen.com/subject/uogmjttx.html