博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
开源一个ReactNative日历控件
阅读量:5788 次
发布时间:2019-06-18

本文共 5590 字,大约阅读时间需要 18 分钟。

项目地址:

演示地址:

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

已经有了为何还需要我这个日历控件?

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

控件需要有何特性

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

开发过程

要开发一个日历控件, 最大的问题就是日期的转换, 虽然Moment.js被很多人使用, 但是Moment使用大量的面向对象的API, 严重影响性能, 这也是在我尝试了Moment之后发现的, 于是就换上了, 轻量级js日期控件, 完全的函数式风格, 在日历控件中只需保存数据, 其他的日期比较/转换等操作都交给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 it    return !!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 find        const 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 page    this.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 page    this.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: () => {},    });  }    // 最外层 
复制代码

其他:

  • 使用 ChineseLunar 来转换中国农历.
  • isTodayVisible 为false时在日历Header上展示一个 button
  • 点击 跳转到今天所在周的页数
  • 最终整个控件的 state 只有 :
this.state = {  datas: this.getInitialDates(), // 保存所有日期,  isTodayVisible: true, // 今天所在周是否在展示  pageOfToday: 2, // 今天在日历的第几页,  从0开始  currentPage: 2, // 当前是日历的第几页,  从0开始};复制代码
  • 所有保存的日期都是 格式, 并且是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测试)

转载地址:http://twhyx.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
mysql-error 1236
查看>>
sshd_config设置参数笔记
查看>>
循序渐进Docker(一)docker简介、安装及docker image管理
查看>>
jsp页面修改后浏览器中不生效
查看>>
大恶人吉日嘎拉之走火入魔闭门造车之.NET疯狂架构经验分享系列之(四)高效的后台权限判断处理...
查看>>
信号量实现进程同步
查看>>
Spring4-自动装配Beans-通过构造函数参数的数据类型按属性自动装配Bean
查看>>
win10.64位wnmp-nginx1.14.0 + PHP 5. 6.36 + MySQL 5.5.59 环境配置搭建 结合Thinkphp3.2.3
查看>>
如何查看python selenium的api
查看>>
Python_Mix*random模块,time模块,sys模块,os模块
查看>>
iframe刷新问题
查看>>
数据解码互联网行业职位
查看>>
我所见的讲的最容易理解,逻辑最强的五层网络模型,来自大神阮一峰
查看>>
vue-cli项目打包需要修改的路径问题
查看>>
js实现复选框的操作-------Day41
查看>>
数据结构化与保存
查看>>
[SpringBoot] - 配置文件的多种形式及优先级
查看>>
chrome浏览器开发者工具之同步修改至本地
查看>>
debian7 + wheezy + chromium + flashplayer
查看>>