项目地址: react-native-slideable-calendar-strip

演示地址: Calendar-Strip.mp4

为何要再实现一个日历控件

已经有了react-native-calendar-strip为何还需要我这个日历控件?

一般的甲方都会在一个页面上拖动拖动, 看到一个日历, 就想滑动切换上下周, 由于react-native-calendar-strip没有滑动特性, 并且在这个issue上讨论了好久, 并没有可行的方案. 于是就萌发自己写一个日历插件的冲动.

控件需要有何特性

  • 左右滑动
  • 农历展示
  • 选中日期
  • 事件标识
  • 下滑手势
  • 回到今日

开发过程

要开发一个日历控件, 最大的问题就是日期的转换, 虽然Moment.js被很多人使用, 但是Moment使用大量的面向对象的API, 严重影响性能, 这也是在我尝试了Moment之后发现的, 于是就换上了datefns, 轻量级js日期控件, 完全的函数式风格, 在日历控件中只需保存Date数据, 其他的日期比较/转换等操作都交给datefns.

其次最头疼的问题是使用FlatList展示数据时候, 如何动态生成新的数据.

在日历控件首次加载时候, 会生成5个周的日期, 将FlatList滚动到中间一页(今天所在的周, 第2页, 从0开始). 当用户滑动到最后一页, 就需要再次生成2个周的数据拼接到尾部, 当用户滑动到第一页, 就需要生成2个周的数据拼接到数组首部, 并且这时候今天所在的页数也会变化, 所以要将今天所在的周的页数+2, 拼接到首部会影响FlatList数据展示, 会展示第一页数据, 此时的第一页数据是最新生成的日期, 所以要滚动到第二页(从0页开始).

  loadPreviousTwoWeek(originalDates) {const originalFirstDate = originalDates[0];const originalLastDate = originalDates[originalDates.length-1];const firstDayOfPrevious2Week = subDays(originalFirstDate, 7 * 2);// 生成两周之前的第一天到原始数据最后一天的日期const eachDays = eachDay(firstDayOfPrevious2Week, originalLastDate);this.setState(prevState => ({datas: eachDays,currentPage: prevState.currentPage+2,pageOfToday: prevState.pageOfToday+2,}), () => {// 悄无声息滚动this.scrollToPage(2, false);});}
复制代码

滑动到最后一页需要加载下两周日期:

//  onEndReached={() => { this.onEndReached(); } }
//  onEndReachedThreshold={0.01}onEndReached() {// console.log('onEndReached');this.loadNextTwoWeek(this.state.datas);}loadNextTwoWeek(originalDates) {const originalFirstDate = originalDates[0];const originalLastDate = originalDates[originalDates.length-1];const lastDayOfNext2Week = addDays(originalLastDate, 7 * 2);const eachDays = eachDay(originalFirstDate, lastDayOfNext2Week);this.setState({ datas: eachDays });}
复制代码

ScrollViewonMomentumScrollEnd属性监听页数变化, 记录今天所在周的页数和当前展示的页数

// onMomentumScrollEnd={this.momentumEnd}
// scrollEventThrottle={500}momentumEnd = (event) => {const firstDayInCalendar = this.state.datas ? this.state.datas[0] : new Date();// 从第一天到今天一共多少天const daysBeforeToday = differenceInDays(firstDayInCalendar, new Date());// ~~向下取整, 第一天到今天一共几周, 也就是今天所在周所在的页数const pageOfToday = ~~(Math.abs(daysBeforeToday / 7));const screenWidth = event.nativeEvent.layoutMeasurement.width;// 通过offset来获取当前所在页数const currentPage = event.nativeEvent.contentOffset.x / screenWidth;// 记录今天所在周页数, 当前展示周的页数, 今天所在周是否被展示this.setState({pageOfToday,currentPage,isTodayVisible: currentPage === pageOfToday,});// 如果滑动到第一页了就需要加载之前两周数据if (event.nativeEvent.contentOffset.x < width) {this.loadPreviousTwoWeek(this.state.datas);}}
复制代码

最棘手的问题是用户点击了日历之外的一个button, 跳转到日历上指定的一天.

  1. 指定日期正好在当前展示的一个周内
  currentPageDatesIncludes = (date) => {const { currentPage } = this.state;const currentPageDates = this.state.datas.slice(7*currentPage, 7*(currentPage+1));// dont use currentPageDates.includes(date); because can't compare Date in itreturn !!currentPageDates.find(d => isSameDay(d, date));}
复制代码

直接设置选中日期为指定日期.

  1. 指定日期不在当前展示周内, 但是当前控件日期数据包含指定日期
    const sameDay = (d) => isSameDay(d, nextSelectedDate);if (this.state.datas.find(sameDay)) {let selectedIndex = this.state.datas.findIndex(sameDay);if (selectedIndex === -1) selectedIndex = this.state.pageOfToday; // in case not findconst selectedPage = ~~(selectedIndex / 7);this.scrollToPage(selectedPage);}
复制代码

找到指定日期所在周的页数, 滚动过去.

  1. 指定日期不在当前展示周内, 并且当前控件日期数据不包含指定日期
if (isFuture(nextSelectedDate)) {const head = this.state.datas[0];const tail = endOfWeek(nextSelectedDate);const days = eachDay(head, tail);this.setState({datas: days,isTodayVisible: false,}, () => {const page = ~~(days.length/7 - 1);// to last pagethis.scrollToPage(page);});
} else {const head = startOfWeek(nextSelectedDate);const tail = this.state.datas[this.state.datas.length - 1];const days = eachDay(head, tail);this.setState({datas: days,isTodayVisible: false,}, () => {// to first pagethis.scrollToPage(0);});
}
复制代码

如果是未来某一天, 那么生成那天所在周的周六到当前日期控件所有日期的第一天之间的所有日期, 找到最后一页, 滚动过去.

如果是之前某一天, 那么生成那天所在周的周日(第一天)到当前日期控件所有日期的最后一天之间的所有日期, 滚动到第一页.

关于 pageOfTodaycurrentPage 交给 momentumEnd() 自动处理.

滚动到页方法是利用 FlatListscrollToIndex 实现:

  scrollToPage = (page, animated=true) => {this._calendar.scrollToIndex({ animated, index: 7 * page });}
复制代码

下滑手势:

  componentWillMount() {const touchThreshold = 50;const speedThreshold = 0.2;this._panResponder = PanResponder.create({onStartShouldSetPanResponder: () => false,onMoveShouldSetPanResponder: (evt, gestureState) => {const { dy, vy } = gestureState;// 滑动距离大雨50, 并且滑动速度大于0.2, 有效下滑if (dy > touchThreshold && vy > speedThreshold) {const { onSwipeDown } = this.props;onSwipeDown && onSwipeDown();}return false;},onPanResponderRelease: () => {},});}// 最外层 <View {...this._panResponder.panHandlers}>
复制代码

其他:

  • 使用 ChineseLunar 来转换中国农历.
  • isTodayVisible 为false时在日历Header上展示一个 button
  • 点击 跳转到今天所在周的页数
  • 最终整个控件的 state 只有 :
this.state = {datas: this.getInitialDates(), // 保存所有日期,isTodayVisible: true, // 今天所在周是否在展示pageOfToday: 2, // 今天在日历的第几页,  从0开始currentPage: 2, // 当前是日历的第几页,  从0开始
};
复制代码
  • 所有保存的日期都是 Date格式, 并且是0点 Wed May 16 2018 00:00:00 GMT+0800 (CST)
  • 控件所需要的props:
CalendarStrip.propTypes = {selectedDate: PropTypes.object.isRequired,onPressDate: PropTypes.func,onPressGoToday: PropTypes.func,markedDate: PropTypes.array,onSwipeDown: PropTypes.func,
};复制代码

PS. 使用datefns另一个好处是, 当传给控件

markedDate = ['2018-01-01', '2018-05-01', '2018-06-01']
复制代码

也是支持的, 不必须传一个Date格式的日期.

如何开源

1. 托管到GitHub

2. 发布到npmjs

3. travis持续集成(jest测试)

开源一个ReactNative日历控件相关推荐

  1. Android开源的精美日历控件,热插拔设计的万能自定义UI,看完直接怼产品经理

    <attr name="current_month_text_color" format="color" /> <!--当前月份的字体颜色-- ...

  2. 一个适用于visual studio 2005的一个js日历控件--zhuan

    最近要用到日历控件刚开始是使用梅花雨3.0不过发现了一些弹出的快慢还有定位的问题.总是有点麻烦.另外我是处于一个隐藏层内所以会有很多问题. 这个时候研究了下discuznt.net看到他们投票里面有一 ...

  3. Android开源的精美日历控件,热插拔设计的万能自定义UI

    UI框架应该逻辑与界面实现分离,该日历控件使用了热插拔的设计 ,简单几步即可实现你需要的UI效果,热插拔的思想是你提供你的实现,我提供我的插座接口,与自定义Behavior是一样的思想. 听说第一页无 ...

  4. android+高仿+日历,Android开源的精美日历控件,热插拔设计的万能自定义UI

    UI框架应该逻辑与界面实现分离,该日历控件使用了热插拔的设计 ,简单几步即可实现你需要的UI效果,热插拔的思想是你提供你的实现,我提供我的插座接口,与自定义Behavior是一样的思想. 听说第一页无 ...

  5. Android开源的精美日历控件,热插拔设计的万能自定义UI,flutter调用原生sdk

    XML用法 如果需要在日历控件下方使用其它控件,使用CalendarLayout控件即可,calendar_content_view_id为其它控件的id,支持任意控件,如RecyclerView.L ...

  6. Android开源的精美日历控件,热插拔设计的万能自定义UI,你值得拥有

    XML用法 如果需要在日历控件下方使用其它控件,使用CalendarLayout控件即可,calendar_content_view_id为其它控件的id,支持任意控件,如RecyclerView.L ...

  7. calendar控件使用 extjs_ExtJs定制日历控件——自动加载更新(日历控件显示)

    将定制的日历控件放到一个Panel里显示,其结构如下: , { border: true, id: 'page', animCollapse : true, width: 1050, height: ...

  8. excel日历弄到html,如何在Excel中利用日历控件来快速输入日期

    给大家分享了一个利用日历控件在Excel单元格中输入日期的小窍门.大家知道,物流和会计行业的报表,经常需要手工输入一些日期,例如发货日期,在途时间.到达配送中心日期.签收日期等,如果手工输入,难免出错 ...

  9. Android考勤日历控件

    工作这么久,第一次在CSDN上写技术博客,望大家多多支持: 本篇博文给大家分享一个考勤日历控件,这里有个需求:要求显示当前月的日期,左右可以切换月份来查看日期.可以通过不同的颜色表示每天的考勤状态: ...

最新文章

  1. MyEclipse搭建java Web项目开发环境
  2. JavaScript 原型继承
  3. sdwan架构怎么搭建?
  4. D3.js比例尺 定量比例尺 之 线性比例尺(v3版本)
  5. 深度学习:tensorflow Layers的实现,numpy实现深度学习(二)
  6. 在SAP CDS view上添加扩展字段后激活,背后的实现原理
  7. django中的admin组件之自定义组件的增删改查的完善
  8. java赋值两个对象数组 clone_Java:类的两个相同对象数组的克隆问题
  9. metal slug java_推荐一款极速CAJ阅读器:稻壳阅读器
  10. soapui oracle groovy,SoapUI Groovy 使用实例
  11. html网页制作特效代码,超全html网页制作特效代码.doc
  12. 【饭谈】自动化有三宝:工资高,福利好,代码和人总有一个能跑
  13. 嵌入式软件设计之设计模式
  14. 高通平台 LCD 的 LK部分代码解析
  15. ssdt函数索引号_shadow ssdt 服务表函数索引
  16. 基于ibeacons三点定位(微信小程序)
  17. 史上最详细域名链接被微信封杀拦截屏蔽解决方案_微信域名防封跳转系统
  18. 通过Excel VBA对序列实现自动分级
  19. 已有a,b两个链表,每个链表中的结点包括学号、成绩。要求把两个链表合并,按学号 升序排序
  20. Mac:Permission denied XXX

热门文章

  1. access 文本转换数字_ACCESS的短文本和长文本分类
  2. 学霸的迷宫-蓝桥杯算法提高-广搜 bfs 经典问题
  3. 计算机网络运用专业,计算机网络专业
  4. Perl的Net::SSH::Perl模块实现远程登陆ssh
  5. SSL证书相关技巧 -- 为什么有时候访问某些加密网站是不需要证书的?
  6. echart3 click点击事件使用
  7. 大道至简——编程的精义
  8. agilent3070软件安装
  9. grayscale实现全站及局部变黑的效果 – 兼容IE/FF等浏览器
  10. ASCII,Unicode,UTF-8,GB2312一些关于编码的理解