Android 原生的 CalendarView 根本无法满足我们日常开发的需要,在开发吾记 APP 的过程中,我觉得需要来一款高性能且美观简洁的日历控件,觉得魅族的日历风格十分适合,于是打算撸一款。
github 地址:
- compile 'com.haibin:calendarview:1.0.2'
先上效果图:
动手之前我们需要分析一下魅族是怎么设计如此高性能的日历的,我们打开开发者选项中的显示布局边界:
好吧,一开始我以为日历界面是 ViewPager+RecyclerView 的,但是这么一看明显就不是了,如果是 RecyclerView,那么我们假设每个月的卡片都有 5*7=35 个 item,每个 item 根布局是 RelativeLayout+3 个 TextView,我们大概估算一下日历初始化时要加载的控件:
3 个 ViewPager 的 item * 35 个 RecyclerView 的 Item * 4(每个 item 的控件数) + 8 (星期栏)= 420+
我的天,这可不能这么干,明显性能大打折扣,我们再来看看月份控件:
好吧,这里看上去就是 ViewPager+RecyclerView 来做的,每个 RecyclerView 的 item 都只是一个控件,里面绘制了文本 ,这里大概就分析清楚了。
我们采取折中的方式,日历界面和月份卡界面均采用 ViewPager+RecyclerView 的方式,不同的是所有的 item 我们都采用自定义 ViewCanvas 绘制的方式来做,这样性能虽然比不上魅族,但速度体验基本差不多,下面先看日历界面的 item 代码:只需要绘制 3 个文本即可
- public class CellView extends View {
- private int mDay = 20;
- private String mLunar;
- private String mScheme;
- private Paint mDayPaint = new Paint();
- private Paint mLunarPaint = new Paint();
- private Paint mSchemePaint = new Paint();
- private Paint mCirclePaint = new Paint();
- private int mRadius;
- private int mCirclePadding;
- private int mCircleColor;
- public CellView(Context context) {
- this(context, null);
- }
- public CellView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mDayPaint.setAntiAlias(true);
- mDayPaint.setColor(Color.BLACK);
- mDayPaint.setFakeBoldText(true);
- mDayPaint.setTextAlign(Paint.Align.CENTER);
- mLunarPaint.setAntiAlias(true);
- mLunarPaint.setColor(Color.GRAY);
- mLunarPaint.setTextAlign(Paint.Align.CENTER);
- mSchemePaint.setAntiAlias(true);
- mSchemePaint.setColor(Color.WHITE);
- mSchemePaint.setFakeBoldText(true);
- mSchemePaint.setTextAlign(Paint.Align.CENTER);
- mCirclePaint.setAntiAlias(true);
- mCirclePaint.setStyle(Paint.Style.FILL);
- TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CellView);
- mDayPaint.setTextSize(array.getDimensionPixelSize(R.styleable.CellView_cell_day_text_size, 18));
- mLunarPaint.setTextSize(array.getDimensionPixelSize(R.styleable.CellView_cell_lunar_text_size, 12));
- mRadius = (int) array.getDimension(R.styleable.CellView_cell_scheme_radius, 8);
- mSchemePaint.setTextSize(array.getDimensionPixelSize(R.styleable.CellView_cell_scheme_text_size, 6));
- mCirclePadding = array.getDimensionPixelSize(R.styleable.CellView_cell_circle_padding, 4);
- mCirclePaint.setColor(array.getColor(R.styleable.CellView_cell_circle_color, 0xff16BB7F));
- array.recycle();
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- int width = getWidth();
- int height = getHeight();
- int w = (width - getPaddingLeft() - getPaddingRight());
- int h = (height - getPaddingTop() - getPaddingBottom()) / 4;
- canvas.drawText(String.valueOf(mDay), w / 2, 2 * h + getPaddingTop(), mDayPaint);
- canvas.drawText(mLunar, w / 2, 4 * h + getPaddingTop(), mLunarPaint);
- if (!TextUtils.isEmpty(mScheme)) {
- canvas.drawCircle(w / 2 + mCirclePadding + mDayPaint.getTextSize(), getPaddingTop() + h, mRadius, mCirclePaint);
- canvas.drawText(mScheme, w / 2 + mCirclePadding + mDayPaint.getTextSize(), getPaddingTop() + mRadius / 2 + h, mSchemePaint);
- }
- }
- /**
- * 初始化日历
- * @param day 天
- * @param lunar 农历
- * @param scheme 事件标记
- */
- void init(int day, String lunar, String scheme) {
- this.mDay = day;
- this.mLunar = lunar;
- this.mScheme = scheme;
- }
- void setTextColor(int textColor) {
- mDayPaint.setColor(textColor);
- mLunarPaint.setColor(textColor);
- }
- void setCircleColor(int circleColor) {
- mCirclePaint.setColor(circleColor);
- invalidate();
- }
- }
月份卡自定义 View
- public class MonthView extends View {
- private int mDiff;//第一天偏离周日多少天
- private int mCount;//总数
- private int mLastCount;//最后一行的天数
- private int mLine;//多少行
- private Paint mPaint = new Paint();
- private Paint mSchemePaint = new Paint();
- private List<Calendar> mSchemes;
- private Calendar mCalendar;
- public MonthView(Context context) {
- this(context, null);
- }
- public MonthView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mPaint.setAntiAlias(true);
- mPaint.setTextAlign(Paint.Align.CENTER);
- mSchemePaint.setAntiAlias(true);
- mSchemePaint.setTextAlign(Paint.Align.CENTER);
- TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MonthView);
- mPaint.setTextSize(array.getDimensionPixelSize(R.styleable.MonthView_month_view_text_size, 12));
- mSchemePaint.setTextSize(array.getDimensionPixelSize(R.styleable.MonthView_month_view_text_size, 12));
- mPaint.setColor(array.getColor(R.styleable.MonthView_month_view_text_color, Color.BLACK));
- mSchemePaint.setColor(array.getColor(R.styleable.MonthView_month_view_remark_color, Color.RED));
- array.recycle();
- measureLine();
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- int width = getWidth();
- int height = getHeight();
- int pLeft = getPaddingLeft();
- int w = (width - getPaddingLeft() - getPaddingRight()) / 7;
- int h = (height - getPaddingTop() - getPaddingBottom()) / 6;
- int d = 0;
- for (int i = 0; i < mLine; i++) {
- if (i == 0) {//第一行
- for (int j = 0; j < (7 - mDiff); j++) {
- ++d;
- canvas.drawText(String.valueOf(j + 1), mDiff * w + j * w + pLeft + w / 2, h, isScheme(d) ? mSchemePaint : mPaint);
- }
- } else if (i == mLine - 1 && mLastCount != 0) {
- int first = mCount - mLastCount + 1;
- for (int j = 0; j < mLastCount; j++) {
- ++d;
- canvas.drawText(String.valueOf(first), j * w + pLeft + w / 2, (i + 1) * h, isScheme(d) ? mSchemePaint : mPaint);
- ++first;
- }
- } else {
- int first = i * 7 - mDiff + 1;
- for (int j = 0; j < 7; j++) {
- ++d;
- canvas.drawText(String.valueOf(first), j * w + pLeft + w / 2, (i + 1) * h, isScheme(d) ? mSchemePaint : mPaint);
- ++first;
- }
- }
- }
- }
- /**
- * 计算行数
- */
- private void measureLine() {
- int offset = mCount - (7 - mDiff);
- mLine = 1 + (offset % 7 == 0 ? 0 : 1) + offset / 7;
- mLastCount = offset % 7;
- }
- /**
- * 初始化月份卡
- * @param mDiff 偏离天数
- * @param mCount 当月总天数
- * @param mYear 哪一年
- * @param mMonth 哪一月
- */
- void init(int mDiff, int mCount, int mYear, int mMonth) {
- this.mDiff = mDiff;
- this.mCount = mCount;
- mCalendar = new Calendar();
- mCalendar.setYear(mYear);
- mCalendar.setMonth(mMonth);
- measureLine();
- invalidate();
- }
- void setSchemes(List<Calendar> mSchemes) {
- this.mSchemes = mSchemes;
- }
- void setSchemeColor(int schemeColor) {
- if (schemeColor != 0)
- mSchemePaint.setColor(schemeColor);
- if(schemeColor == 0xff30393E)
- mSchemePaint.setColor(Color.RED);
- }
- private boolean isScheme(int day) {
- if (mSchemes == null || mSchemes.size() == 0)
- return false;
- mCalendar.setDay(day);
- return mSchemes.contains(mCalendar);
- }
- }
其它代码没有什么难度,日历算法是 github 上找的,更多详情请看仓库地址:
来源: http://www.tuicool.com/articles/RviI3qf