撸一个Android高性能日历控件,高仿魅族
Android原生的CalendarView根本无法满足我们日常开发的需要,在开发吾记APP的过程中,我觉得需要来一款高性能且美观简洁的日历控件,觉得魅族的日历风格十分适合,于是打算撸一款。
github地址:https://github.com/huanghaibin-dev/CalendarView
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个文本即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 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上找的,更多详情请看仓库地址:https://github.com/huanghaibin-dev/CalendarView
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?