周日历
地址: https://github.com/LineChen/Week_Calendar
实现
WeekCalendar是一个继承LinearLayout的ViewGroup,而不是一个View,显示方式完全由使用者控制,类似ListView使用。
关于布局: WeekCalendar是继承自LinearLayout,内部主要有两个直接子控件,星期Layout和日期Layout。星期Layout是一个GridView,日期Layout是一个固定高度的ViewPager。为了让使用者有可以无限滑动的感觉,这里的ViewPager需要做成“无限”的。每一个周布局是一个GridView。
无限ViewPager:
并不是真的无限。实现是在PagerAdapter的getCount()中返回一个较大的数,这里返回的是1000,然后调用ViewPager的setCurrentItem(int)设置为最大值的一半。当然仅仅这样还是不够的。 Jackwharton借鉴了一下ListView的缓存模式,并使用在ViewPager中,所以再添加一个缓存基本就完成了。
缓存中最重要的一个类是RecycleBin,是直接从官方代码中拿出来的。RecycleBin的官方解释是:RecycleBin有助于在布局之间重用视图。 RecycleBin有两个级别的存储:ActiveViews和ScrapViews。 ActiveView是在布局开始时在屏幕上的那些视图。 通过构造行数,他们正在显示当前的信息。 在布局结束时,ActiveView中的所有视图都将降级为ScrapViews。 ScrapViews是可能被适配器使用的旧视图,以避免不必要地分配视图。
结合PagerAdapter就可以让ViewPager使用缓存了:
重写一个RecyclingPagerAdapter继承PagerAdapter,在instantiateItem和destroyItem使用缓存,详细信息请看源码
@Override
public final Object instantiateItem(ViewGroup container, int position) {
int viewType = getItemViewType(position);
View view = null;
if (viewType != IGNORE_ITEM_VIEW_TYPE) {
//先从缓存中获取视图
view = recycleBin.getScrapView(position, viewType);
}
view = getView(position, view, container);
container.addView(view);
return view;
}
@Override
public final void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
container.removeView(view);
int viewType = getItemViewType(position);
if (viewType != IGNORE_ITEM_VIEW_TYPE) {
//在这里将视图缓存
recycleBin.addScrapView(view, position, viewType);
}
}
使用缓存后,只有在开始的时候创建了三个新的视图。
接下来就是显示了~
为了让使用者控制显示布局,定义了一个接口。
public interface GetViewHelper {
View getDayView(int position, View convertView, ViewGroup parent, DateTime dateTime, boolean select);
View getWeekView(int position, View convertView, ViewGroup parent, String week);
}
显示星期:
还记得前面说的显示星期的是个GridView,那么在设置Adapter的时候这样写就可以了:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getViewHelper.getWeekView(position, convertView, parent, weeks.get(position));
}
显示日期:
日期显示稍微复杂一点,最关键的是重写ViewPager的Adapter。
因为有关时间的处理使用的是jodatime,在初始化PagerAdapter的时候会传今天所在这一周的第一天(从周日开始显示),每滑动一页就可以根据这个时间减去或加上一周,jodatime中刚好有相应的api,非常方便。
关键代码:
@Override
public View getView(int position, View convertView, ViewGroup container) {
WeekViewHolder viewHolder;
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(R.layout.item_calendar, container, false);
viewHolder = new WeekViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (WeekViewHolder) convertView.getTag();
}
int intervalWeeks = position - centerPosition;
DateTime start = startDateTime.plusWeeks(intervalWeeks);
final DayAdapter dayAdapter = new DayAdapter(start, getViewHelper, selectDateTime);
viewHolder.weekGrid.setAdapter(dayAdapter);
viewHolder.weekGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectDateTime = dayAdapter.getItem(position);
dayAdapter.setSelectDateTime(selectDateTime);
notifyDataSetChanged();
if(dateSelectListener != null){
dateSelectListener.onDateSelect(selectDateTime);
}
}
});
return convertView;
}
根据position,可以计算出显示的这一周的第一天日期。
int intervalWeeks = position - centerPosition;
DateTime start = startDateTime.plusWeeks(intervalWeeks);
显示一周日期:
初始化一周的日期,
public DayAdapter(DateTime startDateTime, GetViewHelper getViewHelper, DateTime selectDateTime) {
this.getViewHelper = getViewHelper;
this.selectDateTime = selectDateTime;
dateTimes = new ArrayList<>();
for (int i = 0; i < DAYS_OF_WEEK; i++) {
dateTimes.add(new DateTime(startDateTime).plusDays(i));
}
}
日期显示通过接口返回给使用者处理。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getViewHelper.getDayView(position, convertView, parent, dateTimes.get(position), CalendarUtil.isSameDay(dateTimes.get(position), selectDateTime));
}
添加一些公有方法:
setSelectDateTime(DateTime dateTime)
设置选中日期
在PagerAdapter中有一个成员变量是selectDateTime(默认是今天被选中),在显示一周日期的时候会把selectDateTime作为构造参数传递给DayAdapter,在DayAdapter的getView方法中国会判断显示的这一天是不是选中日期。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getViewHelper.getDayView(position, convertView, parent, dateTimes.get(position), CalendarUtil.isSameDay(dateTimes.get(position), selectDateTime));
}
需要注意的是:设置选择日期后要调用PagerAdapter的notifyDataSetChanged()方法,要想这个方法起作用,需要重写PagerAdapter的getItemPosition方法。
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
gotoDate(DateTime dateTime)
跳转到指定日期
相当于重新初始化一遍,只是显示的时间为跳转日期的那一周。
public void gotoDate(DateTime dateTime){
viewPagerContent.setCurrentItem(centerPosition, true);
calendarPagerAdapter.setStartDateTime(dateTime.minusDays(dateTime.getDayOfWeek()));
onWeekChange(centerPosition);
}
getCurrentFirstDay
获取当前页面第一天
根据当前ViewPager的position和centerPosition比较,加上jodatime的api,很简单就实现了。
public DateTime getCurrentFirstDay(){
int intervalWeeks = viewPagerContent.getCurrentItem() - centerPosition;
return calendarPagerAdapter.getStartDateTime().plusWeeks(intervalWeeks);
}
- refresh 刷新界面
这个方法是很有必要的,因为你可能要添加事件,而事件是从网络获取的。
public void refresh(){
calendarPagerAdapter.notifyDataSetChanged();
}
关于监听:
这里提供了两种监听,日期选择监听,周变化监听
public interface DateSelectListener {
void onDateSelect(DateTime selectDate);
}
public interface WeekChangeListener {
void onWeekChanged(DateTime firstDayOfWeek);
}
实现:
- 周变化监听
监听ViewPager页面选择监听,根据position计算出当前显示周的第一天日期。
viewPagerContent.addOnPageChangeListener(new CustomPagerChandeListender() {
@Override
public void onPageSelected(int position) {
onWeekChange(position);
}
});
private void onWeekChange(int position) {
int intervalWeeks = position - centerPosition;
DateTime firstDayofWeek = calendarPagerAdapter.getStartDateTime().plusWeeks(intervalWeeks);
if(weekChangedListener != null){
weekChangedListener.onWeekChanged(firstDayofWeek);
}
}
- 日期选择监听
日期选择监听就是监听GridView的item点击。
viewHolder.weekGrid.setAdapter(dayAdapter);
viewHolder.weekGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectDateTime = dayAdapter.getItem(position);
dayAdapter.setSelectDateTime(selectDateTime);
notifyDataSetChanged();
if(dateSelectListener != null){
dateSelectListener.onDateSelect(selectDateTime);
}
}
});
使用
布局:
<com.beiing.weekcalendar.WeekCalendar
android:id="@+id/week_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:wc_headerBgColor="#ccc"
app:wc_headerHeight="60dp"
app:wc_calendarHeight="55dp" />
代码中:
- 设置布局显示
必须调用
setGetViewHelper
方法加载布局,getDayView
方法控制每一天显示,
getWeekView
方法控制星期显示,使用类似ListView中BaseAdapter中的getView方法。
weekCalendar.setGetViewHelper(new GetViewHelper() {
@Override
public View getDayView(int position, View convertView, ViewGroup parent, DateTime dateTime, boolean select) {
if(convertView == null){
convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_day, parent, false);
}
TextView tvDay = (TextView) convertView.findViewById(R.id.tv_day);
tvDay.setText(dateTime.toString("d"));
if(CalendarUtil.isToday(dateTime) && select){
tvDay.setTextColor(Color.WHITE);
tvDay.setBackgroundResource(R.drawable.circular_blue);
} else if(CalendarUtil.isToday(dateTime)){
tvDay.setTextColor(getResources().getColor(R.color.colorTodayText));
tvDay.setBackgroundColor(Color.TRANSPARENT);
} else if(select){
tvDay.setTextColor(Color.WHITE);
tvDay.setBackgroundResource(R.drawable.circular_blue);
} else {
tvDay.setTextColor(Color.BLACK);
tvDay.setBackgroundColor(Color.TRANSPARENT);
}
ImageView ivPoint = (ImageView) convertView.findViewById(R.id.iv_point);
ivPoint.setVisibility(View.GONE);
for (DateTime d : eventDates) {
if(CalendarUtil.isSameDay(d, dateTime)){
ivPoint.setVisibility(View.VISIBLE);
break;
}
}
return convertView;
}
@Override
public View getWeekView(int position, View convertView, ViewGroup parent, String week) {
if(convertView == null){
convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_week, parent, false);
}
TextView tvWeek = (TextView) convertView.findViewById(R.id.tv_week);
tvWeek.setText(week);
if(position == 0 || position == 6){
tvWeek.setTextColor(getResources().getColor(R.color.colorAccent));
}
return convertView;
}
});
- 设置日期选择监听
weekCalendar.setDateSelectListener(new DateSelectListener() {
@Override
public void onDateSelect(DateTime selectDate) {
String text = "你选择的日期是:" + selectDate.toString("yyyy-MM-dd");
tvSelectDate.setText(text);
}
});
- 设置周变化监听
weekCalendar.setWeekChangedListener(new WeekChangeListener() {
@Override
public void onWeekChanged(DateTime firstDayOfWeek) {
String text = "本周第一天:" + firstDayOfWeek.toString("yyyy年M月d日")
+ ",本周最后一天:" + new DateTime(firstDayOfWeek).plusDays(6).toString("yyyy年M月d日");
tvWeekChange.setText(text);
}
});
其他方法
getSelectDateTime
获取当前选中日期setSelectDateTime(DateTime dateTime)
设置选中日期gotoDate(DateTime dateTime)
跳转到指定日期getCurrentFirstDay
获取当前页面第一天refresh
刷新界面
月日历
地址: https://github.com/LineChen/Month_Calendar
设计与周日历一样,不同的是在日期显示中,一个是按周显示,一个是按月显示。实现中处理的不同主要在DayAdapter中,构造方法中要初始化这个月显示的日期。
public DayAdapter(int calendarHeight, DateTime startDateTime, GetViewHelper getViewHelper, DateTime selectDateTime) {
this.calendarHeight = calendarHeight;
this.getViewHelper = getViewHelper;
this.selectDateTime = selectDateTime;
dateTimes = new ArrayList<>();
final int daysOfMonth = startDateTime.dayOfMonth().getMaximumValue();
int firstDayOfWeek = startDateTime.getDayOfWeek() % DAYS_OF_WEEK;
for (int i = firstDayOfWeek; i >= 1; i--) {
dateTimes.add(new Day(new DateTime(startDateTime).minusDays(i), true));
}
for (int i = 0; i < daysOfMonth; i++) {
dateTimes.add(new Day(new DateTime(startDateTime).plusDays(i), false));
}
DateTime lastDay = dateTimes.get(dateTimes.size() - 1).getDateTime();
int yy = DAYS_OF_WEEK - lastDay.getDayOfWeek() % DAYS_OF_WEEK;
for (int i = 1; i < yy; i++) {
dateTimes.add(new Day(new DateTime(lastDay).plusDays(i), true));
}
}
然后是在getView中会对视图进行高度设置,因为ViewPager高度时固定的,有些月份要显示5行,有些月份显示6行。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Day day = dateTimes.get(position);
day.setSelect(CalendarUtil.isSameDay(day.getDateTime(), selectDateTime));
View view = getViewHelper.getDayView(position, convertView, parent, day);
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = calendarHeight / (dateTimes.size() / DAYS_OF_WEEK);
return view;
}
使用
布局:
<com.beiing.monthcalendar.MonthCalendar
android:id="@+id/month_calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:mc_calendarHeight="@dimen/calender_content_height"/>
代码中:
- 设置布局显示
必须调用
setGetViewHelper
方法加载布局,getDayView
方法控制每一天显示,
getWeekView
方法控制星期显示,使用类似ListView中BaseAdapter中的getView方法。
monthCalendar.setGetViewHelper(new GetViewHelper() {
@Override
public View getDayView(int position, View convertView, ViewGroup parent, Day day) {
if(convertView == null){
convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_day, parent, false);
}
TextView tvDay = (TextView) convertView.findViewById(R.id.tv_day);
DateTime dateTime = day.getDateTime();
tvDay.setText(dateTime.toString("d"));
boolean select = day.isSelect();
if(CalendarUtil.isToday(dateTime) && select){
tvDay.setTextColor(Color.WHITE);
tvDay.setBackgroundResource(R.drawable.circular_blue);
} else if(CalendarUtil.isToday(dateTime)){
tvDay.setTextColor(getResources().getColor(R.color.colorTodayText));
tvDay.setBackgroundColor(Color.TRANSPARENT);
} else if(select){
tvDay.setTextColor(Color.WHITE);
tvDay.setBackgroundResource(R.drawable.circular_blue);
} else {
tvDay.setBackgroundColor(Color.TRANSPARENT);
if(day.isOtherMonth()){
tvDay.setTextColor(Color.LTGRAY);
} else {
tvDay.setTextColor(Color.BLACK);
}
}
ImageView ivPoint = (ImageView) convertView.findViewById(R.id.iv_point);
ivPoint.setVisibility(View.INVISIBLE);
for (DateTime d : eventDates) {
if(CalendarUtil.isSameDay(d, dateTime)){
ivPoint.setVisibility(View.VISIBLE);
break;
}
}
return convertView;
}
@Override
public View getWeekView(int position, View convertView, ViewGroup parent, String week) {
if(convertView == null){
convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_week, parent, false);
}
TextView tvWeek = (TextView) convertView.findViewById(R.id.tv_week);
switch (position){
case 0:
week = "日";
tvWeek.setTextColor(getResources().getColor(R.color.colorAccent));
break;
case 1:
week = "一";
break;
case 2:
week = "二";
break;
case 3:
week = "三";
break;
case 4:
week = "四";
break;
case 5:
week = "五";
break;
case 6:
week = "六";
tvWeek.setTextColor(getResources().getColor(R.color.colorAccent));
break;
}
tvWeek.setText(week);
return convertView;
}
});
- 设置日期选择监听
monthCalendar.setOnDateSelectListener(new OnDateSelectListener() {
@Override
public void onDateSelect(DateTime selectDate) {
tvSelectDate.setText("你选择的日期:" + selectDate.toString("yyyy-MM-dd"));
}
});
- 设置月切换监听
monthCalendar.setOnMonthChangeListener(new OnMonthChangeListener() {
@Override
public void onMonthChanged(int currentYear, int currentMonth) {
tvMonthChange.setText(currentYear + "年" + currentMonth + "月");
}
});
其他方法
getSelectDateTime
获取当前选中日期setSelectDateTime(DateTime dateTime)
设置选中日期gotoDate(DateTime dateTime)
跳转到指定日期getCurrentYear
获取当前显示年份getCurrentMonth
获取当前显示月份refresh
刷新界面