github地址:https://github.com/huanghaibin-dev/CalendarView

推荐一篇讲解CalendarView特别好的博客:https://blog.csdn.net/huanghaibin_dev/article/details/79040147

我的这篇文章重点不是讲解CalendarView怎么使用的,是说我在使用过程中,由于自己脑抽,本来很容易的事情,被自己搞复杂了。尤其是下文第3点,到现在都怀疑当时的自己怎么看资料的。怎么修改“时间选择范围”背景色、结束日期小于开始日期依旧绘制及处理逻辑、根据项目需求实时显示当前年月……,以达到UI效果

 

一:基本属性的使用:

1.日历显示模式有三种(由CalendarLayout控制,直接在布局里面设置,eg:app:calendar_show_mode="both_month_week_view ",):

both_month_week_view //月模式和周模式(默认模式,可切换月周模式)

only_week_view //周模式

only_month_view //月模式

2.设置日历间距calendarView.setCalendarItemHeight

3.想要日历月周模式切换时,下面(指日历下面的布局)的内容跟着上下走动这个梗,一开始接触这个日历时,研究了好久,一直没有做出来,主要是:

1)没有设置calendar_show_mode属性为both_month_week_view;

2)一直误认为com.haibin.calendarviewproject.group.GroupRecyclerView才是真正实现,下面跟随日历上下收缩的关键。其实项目的要求是,日历下面跟随着一个Webview,这个Webview跟随日历收缩而进行上下。到了某天,突然间理解了

以WebView为例,重点是:

(1)在CalendarLayout设置app:calendar_content_view_id="@+id/recyclerView"这个属性,recyclerView就是WebView的id;

(2)WebView必须在CalendarView后面:

<com.haibin.calendarview.CalendarLayout
    android:id="@+id/calendarLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:calendar_show_mode="both_month_week_view "
    app:default_status="shrink"

    app:calendar_content_view_id="@+id/recyclerView">

    <com.haibin.calendarview.CalendarView
        android:id="@+id/calendarView"
        …………
       />

 
<!--会跟随日历月周模式切换,而进行上下走动(也许描述不太好,就是一直跟在日历后面,无论是周还是月模式),这个WebView可以替换成任何控件-->
    <WebView
        android:id="@+id/recyclerView"
        …………
      />

</com.haibin.calendarview.CalendarLayout>

4.回到今天:CalendarView.scrollToCurrent()

5.修改选择范围背景色,如下图所示:

CustomRangeMonthView.java(CustomRangeWeekView.java同理)代码改成如下:
  private int mRadius;
    private int middleColor = CalendarColors.middleColor;//中间范围背景色
    private int selectedColor = CalendarColors.selectedColor;//选择的背景色

    private int middleTxtColor = CalendarColors.middleTxtColor;//中间范围字体颜色
    private int selectedTxtColor = CalendarColors.selectedTxtColor;//选择字体颜色

    public CustomRangeMonthView(Context context) {
        super(context);
    }


    @Override
    protected void onPreviewHook() {
        mRadius = Math.min(mItemWidth, mItemHeight) / 5 * 2;
        mSchemePaint.setStyle(Paint.Style.STROKE);
    }

    /**
     * 绘制选中的日期
     *
     * @param canvas         canvas
     * @param calendar       日历日历calendar
     * @param x              日历Card x起点坐标
     * @param y              日历Card y起点坐标
     * @param hasScheme      hasScheme 非标记的日期
     * @param isSelectedPre  上一个日期是否选中
     * @param isSelectedNext 下一个日期是否选中
     * @return 是否继续绘制onDrawScheme,true or false
     */
    @Override
    protected boolean onDrawSelected(Canvas canvas, Calendar calendar, int x, int y, boolean hasScheme,
                                     boolean isSelectedPre, boolean isSelectedNext) {
        int cx = x + mItemWidth / 2;
        int cy = y + mItemHeight / 2;
        if (isSelectedPre) {
            if (isSelectedNext) {// 中间日期
                mSelectedPaint.setColor(middleColor);
                canvas.drawRect(x, cy - mRadius, x + mItemWidth, cy + mRadius, mSelectedPaint);
            } else {//最后一个,the last 最后一个日期
                mSelectedPaint.setColor(middleColor);
                canvas.drawRect(x, cy - mRadius, cx, cy + mRadius, mSelectedPaint);
                mSelectedPaint.setColor(selectedColor);
                canvas.drawCircle(cx, cy, mRadius, mSelectedPaint);
            }
        } else {
            if(isSelectedNext){
                mSelectedPaint.setColor(middleColor);
                canvas.drawRect(cx, cy - mRadius, x + mItemWidth, cy + mRadius, mSelectedPaint);
            }
            mSelectedPaint.setColor(selectedColor);
            canvas.drawCircle(cx, cy, mRadius, mSelectedPaint);

        }

        return false;
    }

    @Override
    protected void onDrawScheme(Canvas canvas, Calendar calendar, int x, int y, boolean isSelected) {
        int cx = x + mItemWidth / 2;
        int cy = y + mItemHeight / 2;
        canvas.drawCircle(cx, cy, mRadius, 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;

        boolean isInRange = isInRange(calendar);
        boolean isEnable = !onCalendarIntercept(calendar);

        if (isSelected) {
            boolean isSelectedPre = isSelectPreCalendar(calendar);
            boolean isSelectedNext = isSelectNextCalendar(calendar);

            if (isSelectedPre) {
                if (isSelectedNext) {// 中间日期
                    mSelectTextPaint.setColor(middleTxtColor);
                } else {//最后一个,the last 最后一个日期
                    mSelectTextPaint.setColor(selectedTxtColor);
                }
            } else {
                if(isSelectedNext){

                }
                mSelectTextPaint.setColor(selectedTxtColor);
            }
            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() && isInRange && isEnable? mSchemeTextPaint : mOtherMonthTextPaint);

        } else {
            canvas.drawText(String.valueOf(calendar.getDay()), cx, baselineY,
                    calendar.isCurrentDay() ? mCurDayTextPaint :
                            calendar.isCurrentMonth() && isInRange && isEnable? mCurMonthTextPaint : mOtherMonthTextPaint);
        }
    }
CalendarColors.java代码如下(可自定义颜色):
 public static final int middleColor = Color.rgb(255, 221, 217);
    public static final int selectedColor = Color.rgb(255, 83, 67);

    public static final int middleTxtColor = Color.rgb(64, 64, 64);
    public static final int selectedTxtColor = Color.rgb(255, 255, 255);

 

6.日期选择范围,结束日期小于开始日期时,依旧绘制(修改源码三步骤)

在github地址:https://github.com/huanghaibin-dev/CalendarView,下载好项目,运行起来,接下来按照步骤修改

(1)CalendarUtil.java的differ(Calendar calendar1, Calendar calendar2)方法修改成如下:

 /**
     * 运算 大日期减小日期
     * test Pass
     *
     * @param calendar1 calendar1 第二次点击的日期
     * @param calendar2 calendar2 第一次点击的日期
     * @return 大日期减小日期
     */
    static int differ(Calendar calendar1, Calendar calendar2) {
        if (calendar1 == null) {
            return Integer.MIN_VALUE;
        }
        if (calendar2 == null) {
            return Integer.MAX_VALUE;
        }
        java.util.Calendar date = java.util.Calendar.getInstance();

        date.set(calendar1.getYear(), calendar1.getMonth() - 1, calendar1.getDay());//

        long startTimeMills = date.getTimeInMillis();//获得起始时间戳

        date.set(calendar2.getYear(), calendar2.getMonth() - 1, calendar2.getDay());//

        long endTimeMills = date.getTimeInMillis();//获得结束时间戳

        if (startTimeMills < endTimeMills) {
            return (int) ((endTimeMills - startTimeMills) / ONE_DAY);

        } else {
            return (int) ((startTimeMills - endTimeMills) / ONE_DAY);

        }

    }

(2)RangeMonthView.java的onClick(View v)方法,修改某行代码即可,改成如下:(RangeWeekView.java同理)

//大概在168行,因为前面有改过某些代码,不知道准确行数了,如下代码
 int compare = calendar.compareTo(mDelegate.mSelectedStartRangeCalendar);

//改成如下代码:(原因:比较两次日期大小,当第二次点击日期小于第一次点击日期时,会返回负数,反之会放回1,相等时放回0)
int compare = calendar.compareTo(mDelegate.mSelectedStartRangeCalendar);
            if (compare < 0) {
                compare = 1;
            }

(3)RangeMonthView.java的isCalendarSelected(Calendar calendar),修改成如下代码:(RangeWeekView.java同理)

 
    /**
     * 日历是否被选中
     *
     * @param calendar calendar
     * @return 日历是否被选中
     */
    protected boolean isCalendarSelected(Calendar calendar) {
        if (mDelegate.mSelectedStartRangeCalendar == null) {
            return false;
        }
        if (onCalendarIntercept(calendar)) {
            return false;
        }
        if (mDelegate.mSelectedEndRangeCalendar == null) {
            return calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) == 0;
        }

        /*
        *原先的代码如下:
        *(当calendar在大于等于“第一次点击日期”而且小于等于“第二次点击日期”时为true
        *这个逻辑对于我们“第一次点击日期”小于“第二次点击日期”就会返回false了)
        *  return calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) >= 0 &&
        *       calendar.compareTo(mDelegate.mSelectedEndRangeCalendar) <= 0;
        * */
        return (calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) >= 0 &&calendar.compareTo(mDelegate.mSelectedEndRangeCalendar) <= 0) ||
                (calendar.compareTo(mDelegate.mSelectedEndRangeCalendar) >= 0 && calendar.compareTo(mDelegate.mSelectedStartRangeCalendar) <= 0);
    }

前方bug注意:修改三步骤后,结束日期小于开始日期是绘制了,但是不知道你选中的时间范围是什么,解决这个bug,按如下代码修改即可:

1.CalendarViewDelegate.java的getSelectCalendarRange()方法,当结束日期小于开始日期时,置换startTimeMills、endTimeMills即可,改成如下:

  /**
     * 获得选中范围
     *
     * @return 选中范围
     */
    final List<Calendar> getSelectCalendarRange() {
        if (mSelectMode != SELECT_MODE_RANGE) {
            return null;
        }
        List<Calendar> calendars = new ArrayList<>();
        if (mSelectedStartRangeCalendar == null ||
                mSelectedEndRangeCalendar == null) {
            return calendars;
        }
        final long ONE_DAY = 1000 * 3600 * 24;
        java.util.Calendar date = java.util.Calendar.getInstance();

        date.set(mSelectedStartRangeCalendar.getYear(),
                mSelectedStartRangeCalendar.getMonth() - 1,
                mSelectedStartRangeCalendar.getDay());//

        long startTimeMills = date.getTimeInMillis();//获得起始时间戳


        date.set(mSelectedEndRangeCalendar.getYear(),
                mSelectedEndRangeCalendar.getMonth() - 1,
                mSelectedEndRangeCalendar.getDay());//
        long endTimeMills = date.getTimeInMillis();
        //新增的代码,开始日期大于结束日期,置换
        if (startTimeMills > endTimeMills) {
            Long interTimeMills;
            interTimeMills = startTimeMills;
            startTimeMills = endTimeMills;
            endTimeMills = interTimeMills;
        }
        for (long start = startTimeMills; start <= endTimeMills; start += ONE_DAY) {
            date.setTimeInMillis(start);
            Calendar calendar = new Calendar();
            calendar.setYear(date.get(java.util.Calendar.YEAR));
            calendar.setMonth(date.get(java.util.Calendar.MONTH) + 1);
            calendar.setDay(date.get(java.util.Calendar.DAY_OF_MONTH));
            if (mCalendarInterceptListener != null &&
                    mCalendarInterceptListener.onCalendarIntercept(calendar)) {
                continue;
            }
            LunarCalendar.setupLunarCalendar(calendar);
            calendars.add(calendar);
        }
        addSchemesFromMap(calendars);
        return calendars;
    }

 

 

改好后,运行测试。现在问题来了,怎么加入到项目里呢?原先我们引入日历这样子:

implementation 'com.haibin:calendarview:3.6.5'

改好源码后,怎么引入到项目里面呢?我的做法是把calendarview一整个libar引入到项目中,步骤如下:

1.选中calendarview右键——Show in Explorer(就是找到calendarview的路径);

2.选中项目(自己的项目)右键——New——Module——Import Gradle Project——Next——选择calendarview路径——Next;

3.修改calendarview的gradle,

calendarview、minSdkVersion、targetSdkVersion、versionCode、versionName这些值改成跟项目的一样,

把compile改成implementation,androidTestCompile改成androidTestImplementation,testCompiletestImplementation,

注意appcompat-v7、recyclerview-v7、junit版本号跟项目一致

4.clean、rebuild一下,运行即可

 

7.左右滑动切换月份实时显示当前年月

我入的坑:使用的是“范围选择”,要求左右滑动日历(点击<>)时,要显示当前年月,不然用户不知道滑动到哪里去了:

只需继承CalendarView.OnMonthChangeListener的

@Override
public void onMonthChange(int year, int month) {
  //显示年月
}

注意:记得添加calendarView.setOnMonthChangeListener(this);

 

8.设置今天之后的日期不可点且置灰色,重点OnCalendarInterceptListener(select_mode要设置成支持拦截)

 //设置日期拦截事件
    @Override
    public boolean onCalendarIntercept(Calendar calendar) {
        String str = calendar.getYear() + "-" + (calendar.getMonth() < 10 ? "0" + calendar.getMonth() : calendar.getMonth()) + "-" + (calendar.getDay() < 10 ? "0" + calendar.getDay() : calendar.getDay());
        String now= binding.cvHomeDormitoryAttendanceCalendarView.getCurYear() + "-" + binding.cvHomeDormitoryAttendanceCalendarView.getCurMonth() + "-" + binding.cvHomeDormitoryAttendanceCalendarView.getCurDay();
        return TimeUtils.NowCompare(now, str);//现在日期,点击日期进行对比,点击日期>现在日期为true即拦截
    }

    @Override
    public void onCalendarInterceptClick(Calendar calendar, boolean isClick) {
        //点击拦截的日期回调,isClick为true时表示拦截到设置条件的日期,可以做相应提示
    }

目前入过的坑就是以上这些了,并且解决,希望能帮助遇到同样问题的你,后面若遇到新问题,会继续更新!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐