可能是第十好的 Android 开源 日历 Calendar 仿小米,安卓移动开发实验报告
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
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();
}
评论