使用RecycleView實現無限滾動的日曆

anrry發表於2019-04-13

最終效果

使用RecycleView實現無限滾動的日曆
專案地址

一、無限滾動實現

在RecyclerView.Adapter的getItemCount()方法中返回Integer.MAX_VALUE,使用RecycleView的scrollToPosition()方法滾動到一個足夠大的位置,這樣無限滾動效果就實現好了

二、日期顯示

使用RecycleView來實現,需要做的就是填充ItemView,這裡為了獲取資料集方便,以“月”檢視作為ItemView

  • 獲取日期演算法實現
  1. 第一步:需要建立一個變基(包含年份、月份,以當前日期為變基)
  2. 第二步:根據滑動的deltaPosition計算出年份、月份,得到日期後繫結ItemView就很簡單了
public class DateSet {
    private static final String TAG = "DateSet";
    DateCell baseDateCell;
    int basePosition;

    public DateCell getDateCellByPosition(int position, DateCell dc) {
        if (baseDateCell == null) {
            baseDateCell = new DateCell();
            basePosition = position;
            baseDateCell.toCurrentDate();
        }
        int deltaYear = (position - basePosition) / 12;
        int deltaMonth = (position - basePosition) % 12;
        int month = baseDateCell.month + deltaMonth;
        int year = baseDateCell.year + deltaYear;
        if (month <= 0) {
            --year;
            month += 12;
        } else if (month >= 13) {
            ++year;
            month -= 12;
        }
        dc.setDate(year, month);
        LogUtils.d(TAG, "baseDateCell:" + baseDateCell + "\tdc:" + dc);
        return dc;
    }
}
複製程式碼

三、月份檢視實現 這裡實現月份檢視的方法很多種,在這裡我採取的是繼承View,然後使用canvas畫出來 比較煩人的點就是在計算每一天的位置(left,top,right,bottom),這裡為了方便描述就把這個“天”的Cell描述為“DayView”

  • 演算法實現
  1. 第一步:上面已經得到年份,月份後,就可以計算出這個月的第一天是星期幾、這個月有幾周、這個月一共有多少天,畫圖的時候主要用到這幾個資訊;
  2. 第二步:DayView的寬剛好佔據月檢視的1/7,DayView的高度自己可以隨便給一個預設值 結合第一步得到的資訊計算出這一天的(left,top,right,bottom),然後使用Canvas.drawText()方法畫出日期,這裡需要注意drawText的第三個引數是baseLine,如果要使用字型劇中的話,basLine一定要算準確

關於baseLine的相關資訊(圖片來源於網路侵刪)

使用RecycleView實現無限滾動的日曆

計算DayView的(left,right,top,bottom)關鍵程式碼完整程式碼MonthView.java

for (int week = 0; week < weeks/*這個月的總週數*/; week++) {
            int count = (week == 0) ? 7 - firstDayOfWeek/*1號是一個星期的第幾天*/ + 1 : ((week == weeks - 1) ? dateCell.getSumDays() - ((weeks - 2) * 7 + 7 - firstDayOfWeek + 1) : 7);
            for (int index = 0; index < count; index++) {
                String day = String.valueOf(((week == 0) ? index : (week > 1 ? (week - 1) * 7 + 7 - firstDayOfWeek + 1 + index : 7 - firstDayOfWeek + 1 + index)) + 1);
                float l;
                if (week == 0 && firstDayOfWeek > 1) {
                    l = getPaddingLeft() + (firstDayOfWeek - 1 + index) * (getMeasuredWidth() / 7.0F/*day_view_width*/);
                } else {
                    l = getPaddingLeft() + index * (getMeasuredWidth() / 7.0F);
                }
                float t = getPaddingTop() + week * (getMeasuredHeight() / (float) weeks);
                float r = l + getMeasuredWidth() / 7.0F;
                float b = t + getMeasuredHeight() / (float) weeks;
                if (dateCell.isCurMonth() && Integer.parseInt(day) == dateCell.getCurDay()) {
                    canvas.drawCircle(l + (r - l) / 2.0F, t + (b - t) / 2.0F, (r - l) / 4.0F, curDayBgPaint);
                    dayTextPaint.setColor(Color.WHITE);
                } else {
                    dayTextPaint.setColor(Color.BLACK);
                }
                if (week < weeks - 1) {
                    canvas.drawLine(l, b, r, b, bottomLinePaint);
                }
                canvas.drawText(day, l + (r - l) / 2 - dayTextPaint.measureText(day) / 2, t + (b - t) / 2 + (b - t) / 4 - dayTextPaint.getFontMetrics().bottom, dayTextPaint);
                map.put(day, new float[]{l, r, t, b});//儲存位置資訊,用於獲取所點選的具體日期
            }
        }
複製程式碼
  • 月份檢視點選事件的監聽

由於月份檢視我們是直接繼承View,然後使用Canvas話出來的,所以不能簡單的setOnXXXListener;這兒我是Override了View的onTouchEvent方法來;對於獲取點選的具體日期,方法也很簡單,由於月份是一個網格狀的,最簡單的方法就是先橫向遍歷確定點選的點在X軸上落在周幾,然後就是以步長為7來遍歷是在第幾周,這樣也就確定了點選的具體日期

  • 獲取點選的具體日期的演算法
private int getDayByPosition(float x/*點選事件的X座標*/, float y/*點選事件的Y座標*/) {
        if (map.size() == 0) return 0;
        float[] rectInfo;//[left,right,top,bottom]
        for (int day = 1; day < 8; day++) {//先確定點選的點在周幾
            rectInfo = map.get(String.valueOf(day));
            if (x >= rectInfo[0] && x <= rectInfo[1]) {
                while (rectInfo != null && (y < rectInfo[2] || y > rectInfo[3])) {
                    rectInfo = map.get(String.valueOf(day += 7));
                }
                return day > sumDays/*這個月的總天數*/ ? 0 : day;
            }
        }
        return 0;
    }
複製程式碼

到這裡所有基本工作基本完成,接下來就是組裝RecycleView的過程,這裡就不細說了;關於RecycleView吸頂效果實現可以參考其他人的實現方式,只要知道ItemDecoration的三個關鍵方法(getItemOffsets,onDraw,onDrawOver)基本沒問題;

public int getSumWeeksOfMonth() {//獲取一個月的總週數
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, sumDays);
        return calendar.get(Calendar.WEEK_OF_MONTH);
    }

    public int getFirstDayOfWeek() {// 這個月的一號是這周的第幾天,星期天為第一天
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, 1);
        return calendar.get(Calendar.DAY_OF_WEEK);
    }

    private static int getDaysOfMonth(int year, int moth) {//獲取這個月的總天數
        Calendar c = Calendar.getInstance();
        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, moth - 1);
        c.set(Calendar.DATE, 1);
        c.roll(Calendar.DATE, -1);
        return c.get(Calendar.DATE);
    }
複製程式碼

缺陷: 目前還不支援陰曆、節假日顯示

專案地址

相關文章