写点什么

可能是第十好的 Android 开源 日历 Calendar 仿小米,安卓移动开发实验报告

用户头像
Android架构
关注
发布于: 刚刚

private float posY = 0;


/*


  • 触摸事件为了确定点击的位置日期


*/


@Override


public boolean onTouchEvent(MotionEvent event) {


switch (event.getAction()) {


case MotionEvent.ACTION_DOWN:


posX = event.getX();


posY = event.getY();


break;


case MotionEvent.ACTION_UP:


float disX = event.getX() - posX;


float disY = event.getY() - posY;


if (Math.abs(disX) < touchSlop && Math.abs(disY) < touchSlop) {


int col = (int) (posX / cellWidth);


int row = (int) (posY / cellHeight);


onAdapterSelectListener.cancelSelectState();


renderer.onClickDate(col, row);


onAdapterSelectListener.updateSelectState();


invalidate();


}


break;


}


return true;


}


  • 然后看一下 CalendarRenderer 的代码,Renderer 承担了 Calendar 的绘制任务,首先 renderer 根据种子日期 seedDate 填充出 Calendar 包含的 Date 数据,calendar 中持有一个 6*7 二维数组来存放日期数据。然后在 onDraw 的时候通过 IDayRenderer 来完成对日历的绘制。当点击日期改变了日期的状态时,首先改变对应日期的状态 State,然后重绘 Calendar。


private void instantiateMonth() {


int lastMonthDays = Utils.getMonthDays(seedDate.year, seedDate.month - 1); // 上个月的天数


int currentMonthDays = Utils.getMonthDays(seedDate.year, seedDate.month); // 当前月的天数


int firstDayPosition = Utils.getFirstDayWeekPosition(seedDate.year, seedDate.month , CalendarViewAdapter.weekArrayType);


int day = 0;


for (int row = 0; row < Const.TOTAL_ROW; row++) {


day = fillWeek(lastMonthDays, currentMonthDays, firstDayPosition, day, row);


}


}


private int fillWeek(int lastMonthDays, int currentMonthDays, int firstDayWeek, int day, int row) {


for (int col = 0; col < Const.TOTAL_COL; col++) {


int position = col + row * Const.TOTAL_COL; // 单元格位置


if (position >= firstDayWeek && position < firstDayWeek + currentMonthDays) { // 本月的


day ++;


fillCurrentMonthDate(day, row, col);


} else if (position < firstDayWeek) { //last month


instantiateLastMonth(lastMonthDays, firstDayWeek, row, col, position);


} else if (position >= firstDayWeek + currentMonthDays) {//next month


instantiateNextMonth(currentMonthDays, firstDayWeek, row, col, position);


}


}


return day;


}


public void draw(Canvas canvas) {


for (int row = 0; row < Const.TOTAL_ROW; row++) {


if (weeks[row] != null) {


for (int col = 0; col < Const.TOTAL_COL; col ++) {


if (weeks[row].days[col] != null) {


dayRenderer.drawDay(canvas , weeks[row].days[col]);


}


}


}


}


}


public void onClickDate(int col, int row) {


if (col >= Const.TOTAL_COL || row >= Const.TOTAL_ROW)


return;


if (weeks[row] != null) {


if(attr.getCalendarType() == CalendarAttr.CalendayType.MONTH) {


if(weeks[row].days[col].getState() == State.CURRENT_MONTH){


weeks[row].days[col].setState(State.SELECT);


selectedDate = weeks[row].days[col].getDate();


CalendarViewAdapter.saveDate(selectedDate);


onSelectDateListener.onSelectDate(selectedDate);


seedDate = selectedDate;


} else if (weeks[row].days[col].getState() == State.PAST_MONTH){


selectedDate = weeks[row].days[col].getDate();


CalendarViewAdapter.saveDate(selectedDate);


onSelectDateListener.onSelectOtherMonth(-1);


onSelectDateListener.onSelectDate(selectedDate);


} else if (weeks[row].days[col].getState() == State.NEXT_MONTH){


selectedDate = weeks[row].days[col].getDate();


CalendarViewAdapter.saveDate(selectedDate);


onSelectDateListener.onSelectOtherMonth(1);


onSelectDateListener.onSelectDate(selectedDate);


}


} else {


weeks[row].days[col].setState(State.SELECT);


selectedDate = weeks[row].days[col].getDate();


CalendarViewAdapter.saveDate(selectedDate);


onSelectDateListener.onSelectDate(selectedDate);


seedDate = selectedDate;


}


}


}


  • 调用 Renderer 的 draw 方法时使用 dayRenderer.drawDay(canvas , weeks[row].days[col]),dayRenderer 是一个接口,在 lib 中有一个 DayView 的抽象类实现该接口。 其中的 drawDay 方法完成了对该天到 calendar 的 canvas 上的绘制


@Override


public void drawDay(Canvas canvas , Day day) {


this.day = day;


refreshContent();


int saveId = canvas.save();


canvas.translate(day.getPosCol() * getMeasuredWidth(),


day.getPosRow() * getMeasuredHeight());


draw(canvas);


canvas.restoreToCount(saveId);


}


  • 使用继承自 ViewPager 的 MonthPager 来存放 calendar 的 view


viewPageChangeListener = new ViewPager.OnPageChangeListener() {}


//新建 viewPagerChangeListener


@Override


protected void onSizeChanged(int w, int h, int oldW, int oldH) {


cellHeight = h / 6;


super.onSizeChanged(w, h, oldW, oldH);


}//重写 onSizeChanged,获取 dayView 的高度


public int getTopMovableDistance() {


CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) getAdapter();


rowIndex = calendarViewAdapter.getPagers().get(currentPosition % 3).getSelectedRowIndex();


return cellHeight * rowIndex;


}//计算周月切换时在到达选中行之前 MonthPager 收起的距离


public int getRowIndex() {


CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) getAdapter();


rowIndex = calendarViewAdapter.getPagers().get(currentPosition % 3).getSelectedRowIndex();


Log.e("ldf","getRowIndex = " + rowIndex);


return rowIndex;


}//计算选中日期所在的行数


  • 使用 CalendarViewAdapter 为 MonthPager 填充 calendar 的实例


@Override


public void setPrimaryItem(ViewGroup container, int position, Object object) {


super.setPrimaryItem(container, position, object);


this.currentPosition = position;


}


@Override


public Object instantiateItem(ViewGroup container, int position) {


if(position < 2){


return null;


}


Calendar calendar = calendars.get(position % calendars.size());


if(calendarType == CalendarAttr.CalendayType.MONTH) {


CalendarDate current = seedDate.modifyMonth(position - MonthPager.CURRENT_DAY_INDEX);


current.setDay(1);//每月的种子日期都是 1 号


calendar.showDate(current);


} else {


CalendarDate current = seedDate.modifyWeek(position - MonthPager.CURRENT_DAY_INDEX);


if(weekArrayType == 1) {


calendar.showDate(Utils.getSaturday(current));


} else {


calendar.showDate(Utils.getSunday(current));


}//每周的种子日期为这一周的最后一天


calendar.updateWeek(rowCount);


}


if (container.getChildCount() == calendars.size()) {


container.removeView(calendars.get(position % 3));


}


if(container.getChildCount() < calendars.size()) {


container.addView(calendar, 0);


} else {


container.addView(calendar, position % 3);


}


return calendar;


}


  • 日历在切换周月时切换日历中填充的数据

  • 在月模式切换成周模式时,将当前页的 seedDate 拿出来刷新本页数据,并且更新指定行数的周数据,然后得到 seedDate 下一周的周日作为下一页的 seedDate,刷新下一页的数据,并且更新指定行数的周数据。上一页同理

  • 也是说假设我当前选择的是 6 月 12 号周日,处于日历的第二行,也是说下一页的 seedDate 是 6 月 19 号,然后刷新 6 月 19 号所在周的数据到选定的第二行。

  • 当切换周月时,把三页的数据都会重新刷新一遍,以保证数据的正确性。


public void switchToMonth() {


if(calendars != null && calendars.size() > 0 && calendarType != CalendarAttr.CalendayType.MONTH){


calendarType = CalendarAttr.CalendayType.MONTH;


MonthPager.CURRENT_DAY_INDEX = currentPosition;


Calendar v = calendars.get(currentPosition % 3);//0


seedDate = v.getSeedDate();


Calendar v1 = calendars.get(currentPosition % 3);//0


v1.switchCalendarType(CalendarAttr.CalendayType.MONTH);


v1.showDate(seedDate);


Calendar v2 = calendars.get((currentPosition - 1) % 3);//2


v2.switchCalendarType(CalendarAttr.CalendayType.MONTH);


CalendarDate last = seedDate.modifyMonth(-1);


last.setDay(1);


v2.showDate(last);


Calendar v3 = calendars.get((currentPosition + 1) % 3);//1


v3.switchCalendarType(CalendarAttr.CalendayType.MONTH);


CalendarDate next = seedDate.modifyMonth(1);


next.setDay(1);


v3.showDate(next);


}


}


public void switchToWeek(int rowIndex) {


rowCount = rowIndex;


if(calendars != null && calendars.size() > 0 && calendarType != CalendarAttr.CalendayType.WEEK){


calendarType = CalendarAttr.CalendayType.WEEK;


MonthPager.CURRENT_DAY_INDEX = currentPosition;


Calendar v = calendars.get(currentPosition % 3);


seedDate = v.getSeedDate();


rowCount = v.getSelectedRowIndex();


Calendar v1 = calendars.get(currentPosition % 3);


v1.switchCalendarType(CalendarAttr.CalendayType.WEEK);


v1.showDate(seedDate);


v1.updateWeek(rowIndex);


Calendar v2 = calendars.get((currentPosition - 1) % 3);


v2.switchCalendarType(CalendarAttr.CalendayType.WEEK);


CalendarDate last = seedDate.modifyWeek(-1);


if(weekArrayType == 1) {


v2.showDate(Utils.getSaturday(last));


} else {


v2.showDate(Utils.getSunday(last));


}//每周的种子日期为这一周的最后一天


v2.updateWeek(rowIndex);


Calendar v3 = calendars.get((currentPosition + 1) % 3);


v3.switchCalendarType(CalendarAttr.CalendayType.WEEK);


CalendarDate next = seedDate.modifyWeek(1);


if(weekArrayType == 1) {


v3.showDate(Utils.getSaturday(next));


} else {


v3.showDate(Utils.getSunday(next));


}//每周的种子日期为这一周的最后一天


v3.updateWeek(rowIndex);


}


}


  • 使用 CoordinateLayout 的特性来做周月模式切换

  • 1.RecyclerViewBehavior


@Override


public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,


View directTargetChild, View target, int nestedScrollAxes) {


LinearLayoutManager linearLayoutManager = (LinearLayoutManager) child.getLayoutManager();


if(linearLayoutManager.findFirstCompletelyVisibleItemPosition() > 0) {


return false;


}


boolean isVertical = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;


int firstRowVerticalPosition =


(child == null || child.getChildCount() == 0) ? 0 : child.getChildAt(0).getTop();


boolean recycleviewTopStatus = firstRowVerticalPosition >= 0;


return isVertical && (recycleviewTopStatus || !Utils.isScrollToBottom()) && child == directTargetChild;


}


@Override


public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView child,


View target, int dx, int dy, int[] consumed) {


super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);


if (child.getTop() <= initOffset && child.getTop() >= minOffset) {


consumed[1] = Utils.scroll(child, dy, minOffset, initOffset);


saveTop(child.getTop());


}


}


@Override


public void onStopNestedScroll(final CoordinatorLayout parent, final RecyclerView child, View target) {


Log.e("ldf","onStopNestedScroll");


super.onStopNestedScroll(parent, child, target);


if (!Utils.isScrollToBottom()) {


if (initOffset - Utils.loadTop() > Utils.getTouchSlop(context)){


scrollTo(parent, child, minOffset, 200);


} else {


scrollTo(parent, child, initOffset, 80);


}


} else {


if (Utils.loadTop() - minOffset > Utils.getTouchSlop(context)){


scrollTo(parent, child, initOffset, 200);


} else {


scrollTo(parent, child, minOffset, 80);


}


}


}


  • (2)MonthPagerBehavior 当 recyclerView 滑动式,MonthPager 做相应的变化。


@Override


public boolean onDependentViewChanged(CoordinatorLayout parent, MonthPager child, View dependency) {


Log.e("ldf","onDependentViewChanged");


CalendarViewAdapter calendarViewAdapter = (CalendarViewAdapter) child.getAdapter();


if (dependentViewTop != -1) {


int dy = dependency.getTop() - dependentViewTop; //dependency 对其依赖的 vi


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ew(本例依赖的 view 是 RecycleView)


int top = child.getTop();


if( dy > touchSlop){


calendarViewAdapter.switchToMonth();


} else if(dy < - touchSlop){


calendarViewAdapter.switchToWeek(child.getRowIndex());


}


if (dy > -top){


dy = -top;


}


if (dy < -top - child.getTopMovableDistance()){


dy = -top - child.getTopMovableDistance();


}


child.offsetTopAndBottom(dy);


} else {


initRecyclerViewTop = dependency.getTop();


}


dependentViewTop = dependency.getTop();


top = child.getTop();


if((initRecyclerViewTop - dependentViewTop) >= child.getCellHeight()) {


Utils.setScrollToBottom(false);


calendarViewAdapter.switchToWeek(child.getRowIndex());


initRecyclerViewTop = dependentViewTop;


}


if((dependentViewTop - initRecyclerViewTop) >= child.getCellHeight()) {


Utils.setScrollToBottom(true);


calendarViewAdapter.switchToMonth();


initRecyclerViewTop = dependentViewTop;


}


return true;


// TODO: 16/12/8 dy 为负时表示向上滑动,dy 为正时表示向下滑动,dy 为零时表示滑动停止


}


  • 使用 IDayRender 来实现自定义的日历效果


DayView 实现 IDayRenderer,我们新建一个 CustomDayView 继承自 DayView,在里面作自定义的显示


public CustomDayView(Context context, int layoutResource) {


super(context, layoutResource);


dateTv = (TextView) findViewById(R.id.date);


marker = (ImageView) findViewById(R.id.maker);


selectedBackground = findViewById(R.id.selected_background);


todayBackground = findViewById(R.id.today_background);


}


@Override


public void refreshContent() {


renderToday(day.getDate());


renderSelect(day.getState());


renderMarker(day.getDate(), day.getState());


super.refreshContent();


}


使用方法

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
可能是第十好的Android 开源 日历 Calendar 仿小米,安卓移动开发实验报告