square开源日历控件—CalendarPickerView源码导读

本文详细解读了square公司的开源日历控件CalendarPickerView的源码,探讨了其设计理念和实现技巧。控件结构清晰,继承自ViewGroup,使用自定义View实现了日历的渲染。文章通过类比软件开发的角色,阐述了产品经理、UI和高级开发工程师在控件中的体现,揭示了状态更新的优雅实现。
摘要由CSDN通过智能技术生成

前言:公司前阵子更换了一个新的日历控件,如丝般顺滑。看了下发现,竟然出自大名鼎鼎的square!毕竟square出品,必属精品的,所以研读了一番,有了这篇文章。

控件的Git项目在此:android-times-square

整个项目结构比较简单,代码也不复杂:

这里写图片描述

Demo效果图:

这里写图片描述

首先来认识下此日历都由什么构成,建议边看下图,边看下面这段描述:

  1. 整个日历 CalendarPickerView 继承自 ListView

  2. 每个月份MonthView继承自LinearLayout,装了一个title和一个CalendarGridView

  3. CalendarGridView并不是继承GridView,而是一个自定义ViewGroup,内部包含7个CalendarRowView。第一个Row包含7个TextView用于显示星期,其余6个Row(为什么有6个?因为每个月最多可能有6行)各自包含7个CalendarCellView用于显示每周的日期;

  4. CalendarRowView也继承自ViewGroup,内含7个CalendarCellView。值得一提的是3和4这些控件组成都写在MonthView的布局文件:month.xml里;

  5. CalendarCellView是该日历控件的最基本单元,继承自FrameLayout

这里写图片描述

这个5个View就是日历控件的构成,其余类例如:MonthCellDescriptorMonthDescriptor都是一些辅助,这里暂时不表。

下文在导读源码的同时还会分析下控件的设计技巧,为什么会这样设计?如果让我来做,我会怎么做?了解自己跟大神之间的设计思想差异,我觉得这才是阅读源码的精髓。

这里我主要是想列出让我觉得耳目一新的点,并分析实现,然后希望以后自己能学以致用!

首先,每个自定义View的实现都会有麻烦的渲染工作。而纵观此控件所有相关View的源码,渲染工作有条不紊的发布给几个控件,大家各司其职,而另一些View(例如CanlendarCellViewCanlendarRowView)中,只有对外的接口,如此清爽!

再想想我自己写自定义View的时候,各种自定义属性解析,onMeasureonDraw满天飞,不行还得再来个onTouchEvent,然后便堆砌出几千行代码……

下面我将根据控件中一些类的作用,把它们比作软件开发过程中的各个角色,便于大家理解。

产品经理:MonthAdapter + MonthView

MonthView做的工作很简单:根据布局生成日历整体框架,再将MonthAdapter传递的“属性”,进一步传递给所有的CanlendarCellView

上面提到的属性(List<List<MonthCellDescriptor>> cells)是一系列状态的集合,包括:是否高亮、是否选中、是否是今天,在不在所选日期范围内等等,而这些属性最终决定着渲染的外观。

MonthAdapterCalendarPickerView 中。还记得吗, CalendarPickerView 是一个ListView,所以MonthAdapter 也是一个普通ListViewAdapter,它长这样:

@Override public View getView(int position, View convertView, ViewGroup parent) {
      MonthView monthView = (MonthView) convertView;
      if (monthView == null //
          || !monthView.getTag(R.id.day_view_adapter_class).equals(dayViewAdapter.getClass())) {
        monthView =
            MonthView.create(parent, inflater, weekdayNameFormat, listener, today, dividerColor,
                dayBackgroundResId, dayTextColorResId, titleTextColor, displayHeader,
                headerTextColor, decorators, locale, dayViewAdapter);
        monthView.setTag(R.id.day_view_adapter_class, dayViewAdapter.getClass());
      } else {
        monthView.setDecorators(decorators);
      }
      if (monthsReverseOrder) {
        position = months.size() - position - 1;
      }
      monthView.init(months.get(position), cells.getValueAtIndex(position), displayOnly,
          titleTypeface, dateTypeface);
      return monthView;
    }
  }

确实极其普通:MonthView.create(…)根据布局文件month.xml 构建ViewmonthView.init(…)塞数据。months是所有日期数据,它的初始化在这:

    monthCounter.setTime(minCal.getTime());
    final int maxMonth = maxCal.get(MONTH);
    final int maxYear = minCal.get(YEAR);
    while ((monthCounter.get(MONTH) <= maxMonth // Up to, including the month.
        || monthCounter.get(YEAR) < maxYear) // Up to the year.
        && monthCounter.get(YEAR) < maxYear + 1) { // But not > next yr.
      Date date = monthCounter.getTime();
      MonthDescriptor month =
          new MonthDescriptor(monthCounter.get(MONTH), monthCounter.get(YEAR), date,
              monthNameFormat.format(date));
      cells.put(monthKey(month), getMonthCells(month, monthCounter));
      Logr.d("Adding month %s", month);
      months.add(month);
      monthCounter.add(MONTH, 1);
    }

这里minCalminCal,即最小和最大日期都是用户可以设置的。

如果把整个控件的渲染工作看作一项开发工作,MonthAdapterMonthView就是产品。现在产品来提需求了,我的刀呢?!

UI:CanlendarGridViewCanlendar

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值