MaterialCalendar源码分析(上)

来源:互联网 发布:vue.js视频教程百度盘 编辑:程序博客网 时间:2024/05/21 13:23

使用MaterialCalendarView很简单只需要在布局文件中引入MaterialCalendarView,就可以实现横向滑动的日历控件了。
现在从源码分析这个控件,先对整体流程做个介绍,显示几号日期的控件为DayView继承自CheckedTextView,CalendarPagerAdapter类负责将DayView控件添加到CalendarPagerView.
CalendarPagerView继承ViewGroup类抽象类,主要负责日历的单页面绘制,也就是一个月30天的绘制,如下图:
这里写图片描述
在MaterialCalendarView中利用CalendarPager继承自viewPager的类,CalendarPager类利用setAdapter方法,将CalendarPagerView添加到CalendarPager中,最后将CalendarPager添加到MaterialCalendarView.
最后MaterialCalendarView界面的绘制,它继承自ViewGroup,负责添加顶部的topbar,
这里写图片描述
顶部的topbar负责切换月份,以及添加CalendarPagerView,整个日历的主要内容部分。

首先从最基础的控件向上分析,DayView

继承CheckedTextView方法,它的构造方法如下:
public DayView(Context context, CalendarDay day) {
super(context);

    fadeTime = getResources().getInteger(android.R.integer.config_shortAnimTime);    setSelectionColor(this.selectionColor);    setGravity(Gravity.CENTER);    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {        setTextAlignment(TEXT_ALIGNMENT_CENTER);

}

    setDay(day);}

fadeTime 是选择日期时候会有一个选中的背景,当你切换到当月的另一天时候,就会将背景切换到另一天,fadeTime是上一个背景从有到无消失的时间。
每一个显示某月的号的控件都会有一个日期属性。表示几号。
setDay方法就是给DayView设置日期,日期类是一个CalendarDay类,这个类是对
Calendar类的一些封装,使日期的使用便利化。
public void setDay(CalendarDay date) {
this.date = date;
setText(getLabel());
}

public void setDayFormatter(DayFormatter formatter) {    this.formatter = formatter == null ? DayFormatter.DEFAULT : formatter;   ....

}
DayFormatter是一个日期的接口,为便于对日期进行各种转换。
public interface DayFormatter {

String format(@NonNull CalendarDay day);public static final DayFormatter DEFAULT = new DateFormatDayFormatter();

}
DayFormater是为了将CalendarDay转换为字符串的格式
DateFormatDayFormatter实现了DayFormatter接口 默认利用SimpleDateFormat对CalendarDay获取解析,并且获取当前月的第几天。
public class DateFormatDayFormatter implements DayFormatter {

private final DateFormat dateFormat;public DateFormatDayFormatter() {    this.dateFormat = new SimpleDateFormat("d", Locale.getDefault());}public DateFormatDayFormatter(@NonNull DateFormat format) {    this.dateFormat = format;}public String format(@NonNull CalendarDay day) {    return dateFormat.format(day.getDate());}

}

在构造方法中setText方法主要设置是getLabel内容
getLabel方法主要负责将当前日期类中的第几天解析出来.利用DateFormatterDayFormatter类进行解析。
public String getLabel() {
return formatter.format(date);
}

setEnabled方法功能是判断当前dayview控件是可见还是不可见.主要是为实现几种不同的日期显示方式,日期显示方式,每个月最多有 四十二个dayView控件,当每月的一号显示在周五的时候,四十二天控件就会全部显示,

当每月一号显示在周日到周四的时候,则每月的dayview控件显示三十五个,如下图所示:
这里写图片描述

这里写图片描述

而且也可以设置不同的日历模式,每一页日历控件只显示当月的日期,可以显示不是当月的日期,但是不是当月的日期不可以响应点击事件。
isInMonth判断日期是否是显示界面的当月日期,
isInRange判断日期是否在一定的范围内,
isDecoratedDisabled 为是否给DayView扩展新的内容稍后会说到

private void setEnabled() {    boolean enabled = isInMonth && isInRange && !isDecoratedDisabled;    super.setEnabled(enabled);    boolean showOtherMonths = showOtherMonths(showOtherDates);    boolean showOutOfRange = showOutOfRange(showOtherDates) ||                      showOtherMonths;    boolean showDecoratedDisabled = showDecoratedDisabled(showOtherDates);    boolean shouldBeVisible = enabled;    if (!isInMonth && showOtherMonths) {        shouldBeVisible = true;    }    if (!isInRange && showOutOfRange) {        shouldBeVisible |= isInMonth;    }    if (isDecoratedDisabled && showDecoratedDisabled) {        shouldBeVisible |= isInMonth && isInRange;    }    setVisibility(shouldBeVisible ? View.VISIBLE : View.INVISIBLE);}

其中最重要的一个方法就是:applyFacade,这个方法是为了扩展DayView控件的内容.稍后会详细介绍。

DayView方法分析完毕,下面该分析DayView到底添加到哪里去了呢?查看DayView的引用,可知道DayView 在CalendarPagerView里面用的最多,而且也是被添加到CalendarPagerView类里面,下面就分析这个类.
abstract class CalendarPagerView extends ViewGroup implements View.OnClickListener
这个类是一个抽象类,而且继承ViewGroup所以可以添加自己的控件.
它的子类有MonthView,和WeekView,两种日期的显示方式,一种显示一个月的日期,一种是只显示一个星期的日期。

首先看一下它的构造方法:

public CalendarPagerView(@NonNull MaterialCalendarView view, CalendarDay firstViewDay,
int firstDayOfWeek) {
super(view.getContext());
this.mcv = view;
this.firstViewDay = firstViewDay;
this.firstDayOfWeek = firstDayOfWeek;

    setClipChildren(false);    setClipToPadding(false);    buildWeekDays(resetAndGetWorkingCalendar());    buildDayViews(dayViews, resetAndGetWorkingCalendar());

}
构造方法里有三个参数第一个参数是为了和MaterialCalendarView建立联系,给CalendarPaerView添加父布局.
firstViewDay 显示当前月份的第一天,如果显示是三月份,则就是03.01,
firstDayOfWeek,默认值:Calendar.SUNDAY
是日期的排列顺序,默认是按照周日 周一..周六的顺序排列的。

构造方法里面有三个重要的方法就是buildWeekDays resetAndGetWorkingCalendar
和buildDayViews
buildWeekDays第一个方法就是建立星期的顺序,是周日到周六,还是周几到周几,每一种排列,日期的显示就需要做相应的变化。
private void buildWeekDays(Calendar calendar) {
for (int i = 0; i < DEFAULT_DAYS_IN_WEEK; i++) {
WeekDayView weekDayView = new WeekDayView(getContext(), CalendarUtils.getDayOfWeek(calendar));
weekDayViews.add(weekDayView);
addView(weekDayView);
calendar.add(DATE, 1);
}
}
DeFAULT_DAYS_IN默认值为7,WeekDayView就是一个继承TextView的控件,为了显示星期几.
WeekDayView构造方法中的CalendarUtils.getDayofWeek(calendar)参数,表示当前日期是一个星期的第几天,也就是周几。
最后calendar.add(DATE,1)方法就是给传入的calendar变量,加上七天,最后calendar变量变为七天之后的日期。

下面分析resetAndGetWorkingCalendar方法:
protected Calendar resetAndGetWorkingCalendar() {
getFirstViewDay().copyTo(tempWorkingCalendar);
//noinspection ResourceType
tempWorkingCalendar.setFirstDayOfWeek(getFirstDayOfWeek());
int dow = CalendarUtils.getDayOfWeek(tempWorkingCalendar);
int delta = getFirstDayOfWeek() - dow;
//If the delta is positive, we want to remove a week
boolean removeRow = showOtherMonths(showOtherDates) ? delta >= 0 : delta > 0;
if (removeRow) {
delta -= DEFAULT_DAYS_IN_WEEK;
}
tempWorkingCalendar.add(DATE, delta);
return tempWorkingCalendar;
}

这个方法比较绕,例如当前显示的月份是三月份,getFirstViewDay().copyTo(tempWorkingCalendar);就是将当前的月份的第一天,三月一号赋值给tempWorkingCalendar临时变量.
tempWorkingCalendar.setFirstDayOfWeek(getFirstDayOfWeek());
上面set这个方法,是设置一个星期的第一天是以星期几开始的。例如是周日为第一天
int dow = CalendarUtils.getDayOfWeek(tempWorkingCalendar);
Dow获得的值将是3,也就是三月一号为星期二.
int delta = getFirstDayOfWeek() - dow;
getFirstDayOfWeek 也就是Canlendar.SUNDAY = 1;
Delta = -2;
Delta >=0 则执行delta-=DEFAULT_DAYS_WEEK;
如果<0 不执行delta-=DEFAULT_DAYS_WEEK;
接着执行
tempWorkingCalendar.add(DATE, -2);
此时的tempWorkingCalendar为2.28
三月份月的第一行数据就会变成2.27,2.28,3.1 ..
也就是周日 周一将会用2.27,2.28去填充 周二就是3.1

在CalendarPagerView中buildDayViews方法为一个抽象方法,它的子类有MonthView,和WeekView
MonthView,为显示完整的一个月的日期,仅仅分析这种情况,下面为MonthView方法中的代码:
protected void buildDayViews(Collection dayViews,Calendar calendar){
for(int r = 0; r < DEFAULT_MAX_WEEKS; r++){
for(int i = 0; i < DEFAULT_DAYS_IN_WEEK; i++){
//Log.e(“gac”,”buildDayViews…..”);
addDayView(dayViews, calendar);
}
}
}

DEFAULT_MAX_WEEKS;默认值6
DEFAULT_DAYS_WEEK默认值为7

所以会添加42个DayView,如果当月是三月份的话,日期就会从:03-27 一直添加到 4-7

addDayView方法代码如下:
protected void addDayView(Collection dayViews, Calendar calendar) {
CalendarDay day = CalendarDay.from(calendar);
DayView dayView = new DayView(getContext(), day);
dayView.setOnClickListener(this);
dayViews.add(dayView);
addView(dayView, new LayoutParams());

    calendar.add(DATE, 1);}

将日期添加到添加到dayViews 中去,并且添加到CalendarPagerView视图中.

下面分析它的onMeasure方法和onLayout方法
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {

…..

    //The spec width should be a correct multiple

//将父布局的宽度分成七分
final int measureTileSize = specWidthSize / DEFAULT_DAYS_IN_WEEK;

    //Just use the spec sizes    setMeasuredDimension(specWidthSize, specHeightSize);    int count = getChildCount();    for (int i = 0; i < count; i++) {        final View child = getChildAt(i);

//每一个子布局的宽度设置为父布局的七分之一 高度自适应
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
measureTileSize,
MeasureSpec.EXACTLY
);

        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                measureTileSize,                MeasureSpec.EXACTLY        );        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }}

查看onLayout方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

//count=49 其中包含7个星期一到星期日的weekdayview 和42个DayView
final int count = getChildCount();
//换行的时候left重置为0
final int parentLeft = 0;
//子布局的高度
int childTop = 0;
int childLeft = parentLeft;

    for (int i = 0; i < count; i++) {        final View child = getChildAt(i);        final int width = child.getMeasuredWidth();        final int height = child.getMeasuredHeight();

//从左向右排列
child.layout(childLeft, childTop, childLeft + width, childTop + height);

        childLeft += width;        //We should warp every so many children 如果为第七个数据就会换行

并且左边距childLeft = 0 从最左边开始向右重新排列。
if (i % DEFAULT_DAYS_IN_WEEK == (DEFAULT_DAYS_IN_WEEK - 1)) {
childLeft = parentLeft;
childTop += height;
}

    }}

还有下面两个重要方法 等分析扩展dayview内容的时候详细介绍,现在分析最 主要的的流程
void setDayViewDecorators(List results)
invalidateDecorators

由上面可以知道CalendarpagerView完成了dayviews的添加,以及weekview的添加,并且按照当前显示的日期进行排序。显示四十二个DayView控件.

我们继续查找CalendarPagerView到底被谁引用了,你就会看到CalendarPagerAdapter类。继续分析这个类到底是干什么的。

abstract class CalendarPagerAdapter extends PagerAdapter
又是一个抽象的类,其中子类包括两个部分:MonthPagerAdapter,和WeekPagerAdapter
是不是有点熟悉,MonthView,WeekView,两个adpater分别负责两个view。
两种不同的日期显示策略。
首先看一下它的初始化方法:
public Object instantiateItem(ViewGroup container, int position) {
V pagerView = createView(position);

pagerView.setMinimumDate(minDate);
pagerView.setMaximumDate(maxDate);
pagerView.setSelectedDates(selectedDates);

    container.addView(pagerView);    currentViews.add(pagerView);

//添加扩展DayView内容的集合
pagerView.setDayViewDecorators(decoratorResults);

    return pagerView;}

这个为一个pagerView显示的时候初始化方法,核心方法在createView,
createView是一个抽象方法:
我们只分析MothPagerAdapter中的createView
@Override
protected MonthView createView(int position) {
return new MonthView(mcv, getItem(position), getFirstDayOfWeek());
}
还记得MonthView的父类方法吗?CalendarPagerView
public CalendarPagerView(MaterialCalendarView view, CalendarDay firstViewDay,
int firstDayOfWeek)

getItem(position)获取的当前显示月份的月首例如三月就是3.1
getFirstDayOfWeek 获得的是当前日期的星期排列顺序 以周一开头还是周日开头
public CalendarDay getItem(int position) {
return rangeIndex.getItem(position);
}
其中getItem方法中有一个变量rangeIndex 它是DateRangeindex接口的引用,
interface DateRangeIndex {

int getCount();int indexOf(CalendarDay day);CalendarDay getItem(int position);

}

它的实现子类是DateRangeIndex,getItem最后调用的就是DateRangeIndex的getItem方法
public static class Monthly implements DateRangeIndex {

    private final CalendarDay min;    private final int count;

……..
public CalendarDay getItem(int position) {
//dayCache用于缓存当前页的日期
//第一次获取的时候肯定为null
CalendarDay re = dayCache.get(position);
if (re != null) {
return re;
}
Position pagerview当前月份的值例如2016 三月份2401
int numY = position / 12;//当前的位置与最小年份的差值 numy = 200 int numM = position % 12;//当前年份的月份 mumM= 0

        int year = min.getYear() + numY;//当前的年份 1916 日期最小+200         int month = min.getMonth() + numM;//当前的月份 int month = 3        if (month >= 12) {            year += 1;            month -= 12;        }        re = CalendarDay.from(year, month, 1);//最后获取值为当前页的月份的一号        dayCache.put(position, re);        return re;

}

现在日期整个界面差不多已经完成了,就差最后一步了,就是将日期的界面添加到MaterialCalendarView中去了,
MatericalCalendarView利用CalendarPager继承ViewPager类,设置MonthAdapter
来添加每一页的月份界面。
public class MaterialCalendarView extends ViewGroup

首先查看一下构造方法:

public MaterialCalendarView(Context context) {    this(context, null);}public MaterialCalendarView(Context context, AttributeSet attrs) {    super(context, attrs);

//
….
//顶部的topbar的方向按钮
buttonPast = new DirectionButton(getContext());
title = new TextView(getContext());
buttonFuture = new DirectionButton(getContext());
//CalendarPager 添加CalendarPagerAdapter 为了添加CalnedarPagerView
pager = new CalendarPager(getContext());

//将topbar添加到view的最顶端
setupChildren();

//titleChanger 控制标题改变的动画,以及设置标题的值
titleChanger = new TitleChanger(title);
//设置标题日期的解析类
titleChanger.setTitleFormatter(DEFAULT_TITLE_FORMATTER);
//默认填充的是MonthView 默认adapter 为MonthPagerAdapter
adapter = new MonthPagerAdapter(this);

    pager.setAdapter(adapter);

//设置pager 滑动后的透明度变化
pager.setOnPageChangeListener(pageChangeListener);
pager.setPageTransformer(false, new ViewPager.PageTransformer() {
@Override
public void transformPage(View page, float position) {
position = (float) Math.sqrt(1 - Math.abs(position));
page.setAlpha(position);
}
});
…..

//当前月份默认为生活中的的真实月份
currentMonth = CalendarDay.today();
setCurrentDate(currentMonth);

//可视化编辑器会执行下面这代码
if (isInEditMode()) {
removeView(pager);
MonthView monthView = new MonthView(this, currentMonth, getFirstDayOfWeek());
monthView.setSelectionColor(getSelectionColor());
monthView.setDateTextAppearance(adapter.getDateTextAppearance());
monthView.setWeekDayTextAppearance(adapter.getWeekDayTextAppearance());
monthView.setShowOtherDates(getShowOtherDates());
addView(monthView, new LayoutParams(calendarMode.visibleWeeksCount + DAY_NAMES_ROW));
}
}
给MagerialCalendarView添加Topbar
private void setupChildren() {

    topbar = new LinearLayout(getContext());    topbar.setOrientation(LinearLayout.HORIZONTAL);    topbar.setClipChildren(false);    topbar.setClipToPadding(false);    addView(topbar, new LayoutParams(1));    buttonPast.setScaleType(ImageView.ScaleType.CENTER_INSIDE);    buttonPast.setImageResource(R.drawable.mcv_action_previous);    topbar.addView(buttonPast, new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1));    title.setGravity(Gravity.CENTER);    topbar.addView(title, new LinearLayout.LayoutParams(            0, LayoutParams.MATCH_PARENT, DEFAULT_DAYS_IN_WEEK - 2    ));    buttonFuture.setScaleType(ImageView.ScaleType.CENTER_INSIDE);    buttonFuture.setImageResource(R.drawable.mcv_action_next);    topbar.addView(buttonFuture, new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1));    pager.setId(R.id.mcv_pager);    pager.setOffscreenPageLimit(1);    addView(pager, new LayoutParams(calendarMode.visibleWeeksCount + DAY_NAMES_ROW));}

到此整个CalendarView界面分析完了,下一篇分析Decorator Facade者两个接口,主要是为了给DayVIew控件扩展内容的,MaterialCalendarView扩展内容的机制,你先设置好样式,它就会针对你所设置样式的日期,添加相同的内容.例如,你想让某些日期下面显示一些标记例子中给的是给红点,你所设置的日期集合的DayView控件里的内容就会有红点,但是如果你想让一些日期显示红点,另一些日期下面只显示文字不显示红点,这样的功能却无法实现,所以为了试用这一种情况,我做了另一个版本的MaterialCalendarView。最主要为了搞考勤的正常异常的界面.

0 2