Android uses RecycleView to create a Custom Calendar

1 General idea
1) Initialize calendar data and pass it as a list to RecyclerView.Adapter
2) Rewrite the onTouchEvent method of RecyclerView, listen for gesture changes, and then change the list data to re-display the UI

The Last Rendering

2 Key Codes

So the key point of the whole project is how to get the correct date data. This Calendar Tool found on the Internet is really good after testing. It saves a lot of time and can be used directly with a little modification.

public class CalendarTool<T extends BaseDateEntity> {

    private final String TAG = CalendarTool.class.getSimpleName();

    public static int FLING_MIN_DISTANCE = 100;

    private final int[] weekDayRow = {0, 1, 2, 3, 4, 5, 6};

    private ArrayList<DateEntity> mDataList = new ArrayList<>();//Date array
    private ArrayList<T> mRecordList;//Event Record Array
    private DateEntity   mDateEntity;
    private int          mYear;
    private int          mMonth;

    private boolean mEndBelong;
    private boolean mStartBelong;
    private int     mStartDay;
    private int     mEndDay;

    /**
     * Current year, month and day
     */
    private int mCurrenYear;
    private int mCurrenMonth;
    private int mCurrenDay;

    /**
     * Array of Monthly Days in Ordinary Years
     */
    int commonYearMonthDay[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    /**
     * An array of leap years, months and days
     */
    int leapYearMonthDay[]   = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    public CalendarTool() {
        /** Date of initialization of the current system */
        Calendar calendar = Calendar.getInstance();
        mCurrenYear = calendar.get(Calendar.YEAR);
        mCurrenMonth = calendar.get(Calendar.MONTH) + 1;
        mCurrenDay = calendar.get(Calendar.DAY_OF_MONTH);
        this.mYear = mCurrenYear;
        this.mMonth = mCurrenMonth;
    }

    /**
     * Get the current calendar year x for the year and y for the month
     */
    public Point getNowCalendar() {
        Point p = new Point(mYear, mMonth);
        return p;
    }

    /**
     * Judging whether the first day belongs to this month or not
     */
    public boolean isStartBelong() {
        return mStartBelong;
    }

    /**
     * Judging whether the last day belongs to this month or not
     */
    public boolean isEndBelong() {
        return mEndBelong;
    }

    /**
     * Get the date of the first day of the calendar
     */
    public int getStartDay() {
        return mStartDay;
    }

    /**
     * Get the date of the last day of the calendar
     */
    public int getEndDay() {
        return mEndDay;
    }

    public ArrayList<DateEntity> initDateList() {
        return initDateList(mYear, mMonth);
    }

    public void initRecordList(ArrayList<T> recordList) {
        mRecordList = recordList;
    }

    /**
     * Get the date set of the current page by year and month
     */
    private ArrayList<DateEntity> initDateList(int year, int month) {

        Log.i(TAG, "initDateList: year = " + year + " month = " + month);

        mDataList.clear();

        /** Amendment */
        int endDate = 0;// Get the number of days from the previous month as the end date of the previous month in this calendar
        if ((year - 1) == this.mYear || month == 1) {// If the number of days in the last month is the number of days in December of the previous year, or when it turns to January, the number of days in the last month is also the number of days in December of the previous year.
            endDate = this.getDays(year - 1, 12);
        } else {// Get the number of days from the previous month as the end date of the previous month in this calendar
            endDate = this.getDays(year, month - 1);
        }
        /** End of Modification */

        this.mYear = year;// The year on the current calendar
        this.mMonth = month;// Months on the current calendar

        int days = this.getDays(year, month);// Get the total number of days this month
        int dayOfWeek = this.getWeekDay(year, month);//What day is the first day of the current year?
        int selfDaysEndWeek = 0;// What day is the last day of the month?

        mStartBelong = true;

        /** First add the ones that are not part of this month */
        if (dayOfWeek != 0) {
            int startDate = endDate - dayOfWeek + 1;// The last month of the current month begins on this calendar
            for (int i = startDate, j = 0; i <= endDate; i++, j++) {

                mDateEntity = new DateEntity(year, month - 1, i);
                mDateEntity.date = mDateEntity.year * 10000 + mDateEntity.month * 100 + i;
                if (startDate == i) {
                    mStartBelong = false;
                    mStartDay = mDateEntity.date;
                }

                mDateEntity.isSelfMonthDate = false;
                mDateEntity.weekDay = weekDayRow[j];
                mDateEntity.hasRecord = hasRecord(mDateEntity.date);
                mDataList.add(mDateEntity);
            }
        }

        /** Add this month's */
        for (int i = 1, j = dayOfWeek; i <= days; i++, j++) {

            mDateEntity = new DateEntity(year, month, i);
            mDateEntity.date = mDateEntity.year * 10000 + mDateEntity.month * 100 + i;
            if (mStartBelong && i == 1) {
                mStartDay = mDateEntity.date;
            }
            if (i == days) {
                mEndDay = mDateEntity.date;
            }
            mDateEntity.isSelfMonthDate = true;
            if (j >= 7) {
                j = 0;
            }
            selfDaysEndWeek = j;
            mDateEntity.weekDay = weekDayRow[j];
            if (year == mCurrenYear && month == mCurrenMonth && i == mCurrenDay) {
                mDateEntity.isNowDate = true;
            }
            mDateEntity.hasRecord = hasRecord(mDateEntity.date);
            mDataList.add(mDateEntity);
        }

        mEndBelong = true;

        /*** Add the next month's */
        for (int i = 1, j = selfDaysEndWeek + 1; i < 7; i++, j++) {

            if (j >= 7) {
                break;
            }
            mEndBelong = false;

            mDateEntity = new DateEntity(year, month + 1, i);

            if (mDateEntity.month > 12) {
                mDateEntity.year = year + 1;
                mDateEntity.month = 1;
            }
            mDateEntity.date = mDateEntity.year * 10000 + mDateEntity.month * 100 + i;
            mDateEntity.isSelfMonthDate = false;
            mDateEntity.weekDay = weekDayRow[j];
            mDateEntity.hasRecord = hasRecord(mDateEntity.date);
            mDataList.add(mDateEntity);

            mEndDay = mDateEntity.year * 10000 + mDateEntity.month * 100 + i;
        }
        return mDataList;
    }

    /**
     * How many days are there in a month?
     */
    private int getDays(int year, int month) {
        int days = 0;

        if ((year % 4 == 0 && (year % 100 != 0)) || (year % 400 == 0)) {
            if (month > 0 && month <= 12) {
                days = leapYearMonthDay[month - 1];
            }
        } else {
            if (month > 0 && month <= 12) {
                days = commonYearMonthDay[month - 1];
            }
        }
        return days;
    }

    private boolean hasRecord(int date) {
        if (mRecordList != null) {
            for (T baseDateEntity : mRecordList) {
                if (baseDateEntity.year * 10000 + baseDateEntity.month * 100 + baseDateEntity.day == date) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Through the year, the first day of month acquisition is the week, return 0 is Sunday, 1 is Monday, and so on.
     */
    private int getWeekDay(int year, int month) {
        int dayOfWeek;
        int goneYearDays = 0;
        int thisYearDays = 0;
        boolean isLeapYear = false;//Leap year
        int commonYearMonthDay[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        int leapYearMonthDay[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        for (int i = 1900; i < year; i++) {// From 1900 onwards, January 1, 1900 was Monday.
            if ((i % 4 == 0 && (i % 100 != 0)) || (i % 400 == 0)) {
                goneYearDays = goneYearDays + 366;
            } else {
                goneYearDays = goneYearDays + 365;
            }
        }
        if ((year % 4 == 0 && (year % 100 != 0)) || (year % 400 == 0)) {
            isLeapYear = true;
            for (int i = 0; i < month - 1; i++) {
                thisYearDays = thisYearDays + leapYearMonthDay[i];
            }
        } else {
            isLeapYear = false;
            for (int i = 0; i < month - 1; i++) {
                thisYearDays = thisYearDays + commonYearMonthDay[i];
            }
        }
        dayOfWeek = (goneYearDays + thisYearDays + 1) % 7;

        Log.d(this.getClass().getName(), "From 1990 to the present" + (goneYearDays + thisYearDays + 1) + "day");
        Log.d(this.getClass().getName(), year + "year" + month + "month" + 1 + "Days are weeks." + dayOfWeek);
        return dayOfWeek;
    }

    public void flushDate(float distance_x) {
        if (distance_x < 0) {// Fling right
            if (mMonth + 1 > 12) {
                mDataList = initDateList(mYear + 1, 1);
            } else {
                mDataList = initDateList(mYear, mMonth + 1);
            }
        } else {// Fling left
            if (mMonth - 1 <= 0) {
                mDataList = initDateList(mYear - 1, 12);
            } else {
                mDataList = initDateList(mYear, mMonth - 1);
            }
        }
    }
}

The key part is the initDateList method, which calculates the data to be displayed in the current calendar based on the current incoming annual and monthly data, including the date of the previous month and the date of the next month. So all APP has to do is continue to import year and month information and recalculate calendar data.
. Here, the onTouchEvent of RecyclerView method is rewritten to determine the user's touch direction. Later, to achieve each calendar click event, it conflicts with the monitored sliding event.
So the onInterceptTouchEvent method is also rewritten to intercept click events of child controls.

public class CalendarRecycleView<T extends BaseDateEntity> extends RecyclerView {

    private CalendarRecycleViewAdapter mAdapter;
    private Context                    mContext;
    private CalendarTool               mCalendarTool;
    private OnCalendarDateListener     mDateListener;
    private float                      motion_x;

    public CalendarRecycleView(Context context) {
        super(context);
        mContext = context;
        init();
    }

    public CalendarRecycleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    public CalendarRecycleView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        init();
    }

    private void init() {
        setLayoutManager(new StaggeredGridLayoutManager(7, StaggeredGridLayoutManager.VERTICAL));
        mCalendarTool = new CalendarTool();
        mAdapter = new CalendarRecycleViewAdapter(mContext, mCalendarTool.initDateList());
        setAdapter(mAdapter);
        mAdapter.setOnItemListener(new CalendarRecycleViewAdapter.OnItemListener() {
            @Override
            public void onItemClick(DateEntity dateEntity) {
                if (mDateListener != null) {
                    mDateListener.onDateItemClick(dateEntity);
                }
            }
        });
    }

    public void initRecordList(ArrayList<T> list) {
        mCalendarTool.initRecordList(list);
        mCalendarTool.initDateList();
        mAdapter.notifyDataSetChanged();
    }


    public void setOnCalendarDateListener(OnCalendarDateListener listener) {
        this.mDateListener = listener;
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                motion_x = event.getX();
                Log.i("onTouchEvent", "ACTION_DOWN: " + event.getX() + "  " + motion_x);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i("onTouchEvent", "ACTION_MOVE: " + event.getX() + "  " + motion_x);
                float x = event.getX() - motion_x;
                if (Math.abs(x) > CalendarTool.FLING_MIN_DISTANCE && motion_x != 0) {
                    mCalendarTool.flushDate(x);
                    mAdapter.notifyDataSetChanged();
                    motion_x = 0;
                    if (mDateListener != null) {
                        mDateListener.onDateChange(mCalendarTool.getNowCalendar(),
                                mCalendarTool.getStartDay(),
                                mCalendarTool.getEndDay(),
                                mCalendarTool.isStartBelong(),
                                mCalendarTool.isEndBelong());
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;

        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                motion_x = event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                if (Math.abs(motion_x - event.getX()) >= mCalendarTool.FLING_MIN_DISTANCE) {
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }
}

3 Conclusion
It's easy to implement, that is, pay more attention to the details, and then because the ViewPager is not used, there is no animation effect in sliding, and it's also easy to add, the key is to calculate the list data of the ViewPager fragment.

Keywords: Fragment

Added by ivory_kitten on Thu, 20 Jun 2019 01:11:56 +0300