日历的实现

近期由于工作需要,对以前的日历控件进行重构和加强。
日历总体使用PopupWindow实现,日历view通过popupWindow.setContentView实现。

原有日历使用的view为单一的view(FrameLayout:calendarView),在功能上不支持滑动,仅支持左右button点击切换月份,支持设置可选范围、支持点击选中日期。
基于此,实现上为calendarView中包含GridView,更改月份时直接更换adapter中的月份数据在同一页面进行内容的更换,该实现体验较差,生硬死板。

目标日历需要新增实现手势滑动、主题更换、设置展现动画、标记当日日期等效果。
使用了ViewPager实现滑动效果,使用PageAdapter作为适配器。ViewPager容器中的每一个页面为一个CalendarPageView类(LinearLayout),承载日历滑动部分的view显示。

其中需要解决的主要问题为:viewPager在滑动过程中的动态加载和数据预加载问题。
由于正常情况下,viewPager的滑动所显示的内容是相对固定的,比如3到4个界面,负责展示即可。
但是对于日历来说,事情要复杂得多;具体问题有以下几点。

日历的页数数量处理

日历,可以理解为页面是无穷的,按照当今的日历,早可到1970年,晚可到无穷光年之外。为了解决这种无穷的问题,需要换个角度思考:用户的滑动是有限的。即时实现了加载无数页面的情况,用户也不可能全都滑一遍,因此,在用户所能用到的范围内进行设定即可。这里设定1000页(够你滑的了吧。。。),并且将第500页设置为当前日期所在的月份(这样,当月永远在第500页,前后永远对称)。

日历的页面缓存

使用过ViewPager及FragmentPagerAdapter的都知道,在Adapter内默认的是缓存3个页面,这三个页面分别是当前显示的页面,当前左边的页面和当前右面的页面,这是为了实现滑动时候的流畅。这个缓存机制是在基类中已经存在的。

在FragmentPagerAdapter中暴露了getItem()方法用于加载需要的界面,以及getCount()用于设置总页数(上面提到的1000),还可以重写destroyItem()进行页面销毁的控制。除此之外还要实现基类的构造函数,需要传入FragmentManager fm;这也成为了使用FragmentPagerAdapter(包括后面提到的FragmentPagerStatAdapter)的致命弱点。

而如果使用的是PagerAdapter,则可重写instantiateItem()设置界面的加载,destroyItem()设置界面的删除过程,当然还有getCount()设置总页数和isViewFramObject()

对比以上两种(加上FragmentPagerStatAdapter,共三种),最终选择PagerAdapter。原因就是,前两种都需要构造函数传入FragmentManager对view进行管理,这就使得,能够使用该viewpager的Activity只能是FragmentActivity,因为只有这样才能从该Activity中通过getSupportFragmentManager()获得fragmentManager对象。如果我要将viewPager放在一个PopupWindow中就无法获得了,因此是有局限性的。

敲定Adapter之后,我们来讨论如何做到页面缓存。首先需要明确,adapter时刻都需要保持三个页面的引用,那我们必须在任何一个时刻都有至少4个页面。为什么是四个,我们看一下viewPager如何通过Adapter获取每一个page。

如上图所示,在开始左滑时,首先调用instantiateItem()将4号页面设置好,再调用destroyItem()删除最左侧的1号页面,然后动画将2,3,4这三个页面滑动到正确的位置。这也就是为何需要至少4个页面的原因。新增和删除页面的执行不是同时的,有先有后,就必然有个顶上和换下的过程,因此需要留下一个buffer。
在日历中我使用List < CalendarViewPage >作为这四个页面的缓存,同时使用position%4获取此时应该使用的缓存对象。页面缓存问题不复存在。

日历数据的缓存

说完页面的缓存,来说一下日历数据的缓存。每一个page页面中都包含了日历的小格子,即使用GridView进行实现。对于日历的数据,涉及到计算问题,即使用一个日历Helper类计算某年某月某日的所有日子和位置。幸好有MonthDisplayHelper的支持,能够获得日历页面中的所有日期数字,包括当月页面的上月日期和下月日期。因此每个月的日期数据使用List 存储即可。

那么问题来了,每个月的日期都需要MonthDisplayHelper计算获得,计算过程还是比较费时的。因此考虑使用List 对每个月份的日期数据进行存储,在涉及到点击事件或切换页面引发的UI变化时,及时刷新对应的MonthData状态位即可。

思路出来了,在缓存中,其实使用了SparseArray这一个类似HashMap的结构进行缓存,它的key只能是数字,并且比HashMap更高效。这里key为viewPager的position的值。前面提到过,position一共1000,中点为500,也就是说,当月数据在SparseArray中对应的key=500.

当在加载page页面的时候需要某个月的MonthData时,先检查SparseArray中是否有缓存,如果有则直接获取根据当前状态刷新一遍;否则重新计算MonthData,在计算的过程中根据当前状态值进行设定,然后存入缓存。

自此,日历数据缓存搞定。

ViewPager的position如何关联缓存数据

前面制定的规则是,1000个页面滚动容量,当月放在中间点,即500处。那么,使用Helper类是可以计算出当前月之前的某年某月的距离数,或者今后某年某月距现在的月份差,使用500去计算吧,你懂得。

说是坑,其实也不是,只能是对于工具类的掌握不够熟练造成的吧。例如:
计算当前年月日的时候,使用Calendar类,通过getInstance().get()获取当前年,月,日,都是int类型,但没料到,获得的月刚好比正常月少1,坑吧,年和日都不差,只有月份差1。还有使用GregorianCalendar获取13位时间戳的时候,传入的年月日竟然也要求月份为减1以后的值。。
在这上面被坑调了半天。。

还有一个大坑,由于业务需要,在打开popupWindow的时候需要设置某年某月某日,这就需要打开的时候,viewPager.setCurrentItem(position)到指定的position。那么问题来了,当我初次打开日历时,可能position刚好设置为当前月,或者其他月都行,暂且当做a月,(注意初次这个词),当我翻到几个月之前,当做b月,然后关闭popupWindow。由于popupWindow不是每次打开都会new一个新的(太浪费),因此之前的状态都还在内存,下次打开理论上是应该正常显示为a月的界面。但是,当我再次打开时,发现有的时候a月的页面竟然全无数据成为空白,而且这种情况不是必现,而是时而有时而无。而且该情况只发生在我向前滑动的时候,从来不会发生在向后滑动日历的情况中。

聪明的你想到什么原因了吗?

原因请看下图:

图中方框内为月份数字,上面的数字表示缓存view的index。
如果当前是6月份,上一次popwindow关闭的时候我停留在2月份。当我再次打开pageView时,设置到6月的位置,因此adapter会通过instentiaItem()方法加载1,2,3号缓存页面,加载完毕后,需要执行deleteItem()方法删除原有存留页面,而此时,删除的将是1月,2月和3月份的页面,而他们刚好又对应的1,2,3页面,因此他们的view内数据都会被取消关联,这是展现的6月份就是没有数据的。正是因为这种先加载再删除的adapter机制导致了这个错误。如果你尝试将上一次的滑动结果停留在3月或者一月,总之只要是index=2的页面被加载进来过,第二次打开直接到6月份都会出现这个空白问题

介于这个问题,在关闭的时候又没有找到能够清除已加载页面的方式,我做了以下两步:

  1. 删除Adapter
  2. new 一个新的Adapter
  3. 将状态设置到该新的adapter中
  4. setAdapter
    这样就完全避免受到过时状态的影响了。

实现中的不足

实现过程中,我意识到,数据的缓存会成为一个消耗内存的问题。因为我只有添加缓存,并没有删除缓存的机制,除非用户关闭日历,我就可以通过删除Adapter来将内存释放,否则如果日历一直开着,用户一直朝着一个方向猛滑,早晚得OOM(这种人就2B)。

我的想法是使用LRU结构代替现有SparseArray进行定期清理。但介于2B还是少,我还木有替换。
以上是本次日历增强过程中的重点,希望读到它的人今后遇到问题能收到启发,少走弯路。

写了这么多,该下班了。各位晚安。

android实现日历相关推荐

  1. android 日历翻页动画,Android 仿日历翻页、仿htc时钟翻页、数字翻页切换效果

    废话不多说,效果图: 自定义控件找自网络,使用相对简单,具体还没有来得及深入研究,只是先用笨方法大概实现了想要的效果,后续有空会仔细研究再更新文章, 本demo切换方法是用的笨方法,也就是由新数字和旧 ...

  2. Android移动开发-Android开发日历时常用的农历和公历换算代码工具类

    下面是与Android开发日历时常用的有关农历计算.公历计算.二十四气节相关的代码工具类的代码. Constant.java逻辑代码如下: package com.fukaimei.calendar. ...

  3. android+高仿+日历,项目源码--Android天气日历精致UI源码

    技术要点: 1. 天气日历精致UI 2. Android的Http通信技术 3. Android的天气信息解析 4. Android的日历信息的统计 5. Andorid的地理位置的管理 6.源码带有 ...

  4. android 仿旅游日历控件_可能是第十好的Android 开源 日历 Calendar 仿小米

    简介 由于项目的需求,研究了众多日历软件.本软件是一款高仿小米的自定义日历>控件,周月视图平滑滚动,平滑切换,可以在xml文件中进行属性的配置定制,加入你自己的RecyclerView后,实现日 ...

  5. android 人生日历,android版人生日历日子怎么用 安卓版人生日历日子使用教程

    人生日历android版新发3.3.05.10版本,新增日子功能,那么android版人生日历日子怎么用呢?今天小编就为大家分享安卓版人生日历日子使用教程,一起来看看吧! 人生日历的日子,设计成四叶草 ...

  6. android程序日历layout,Android使用GridLayout绘制自定义日历控件

    效果图 思路:就是先设置Gridlayout的行列数,然后往里面放置一定数目的自定义日历按钮控件,最后实现日历逻辑就可以了. 步骤: 第一步:自定义日历控件(初步) 第二步:实现自定义单个日期按钮控件 ...

  7. 自定义日历控android,Android自定义日历Calender代码实现

    产品要做签到功能,签到功能要基于一个日历来进行,所以就根据 要求自定义了一个日历 自定义控件相信做android都知道: (1)首先创建一个类,继承一个容器类或者是一个控件 (2)然后就是你需要设置的 ...

  8. android studio日历小程序,android studio无法加载日历界面

    LayoutInflater inflater= (LayoutInflater) getApplicationContext().getSystemService(LAYOUT_INFLATER_S ...

  9. android自定义选年控件,Android精美日历控件CalendarView自定义使用完全解析

    项目github地址 此框架采用组合的方式,各个模块互相独立,可自由采用各种提供的控件组合,完全自定义自己需要的UI,周视图和月视图可通过简单自定义任意自由绘制,不怕美工提需求!!!下面教程将介绍如何 ...

  10. android 本地日历,Android日历提供商:如何删除自己的本地日历?

    我正在学习如何使用Android日历.到目前为止,我能够显示有关现有日历的信息.我也可以创建自己的本地日历 - 测试代码如下: private void createCalendarTest() { ...

最新文章

  1. 一不小心又把应用发挂了,复盘一下这十几分钟的黑暗时刻
  2. 滇西应用技术大学计算机专业录取分数线,滇西应用技术大学录取分数线2021是多少分(附历年录取分数线)...
  3. java 开发人员工具_Java开发人员的5种工具
  4. React开发(123):ant design学习指南
  5. jdk中java_怎样使用JavaJDK中Java?
  6. oracle 存储过程打印语句,oracle学习之第一个存储过程:打印Hello World
  7. webpack打包流程_了不起的 Webpack 构建流程学习指南
  8. Module LUT6 is not defined
  9. 产品运营:当你和上级发生争执你会怎么处理?
  10. css中绝对定位与相对定位的区别
  11. 在Microsoft Office 2007中检测和修复应用程序
  12. 免费且好用的GIF录制软件LICEcap
  13. Unity app 如何打开商店
  14. RDD优化--RDD共享变量(广播变量与累加器)
  15. 拼多多店铺的先用后付|盛天海科技
  16. webService--java
  17. 码洞原创深度技术文章大全 —— 高端面试必备
  18. 打工人的「周游世界」,AI算法帮你实现,泪目!
  19. 安装双系统时进行多重引导,最好先安装Windows再装Linux
  20. 电脑云便签怎么设置语音电话提醒待办事项?

热门文章

  1. window11 网络突然就用不了,系统更新网络就用不了了,DNS服务器可能不可用
  2. 数据库服务器对硬件配置的五个要求
  3. emacs常用配置-Hippie-expand自动补全
  4. 烤薯条的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  5. Linux下更新Chrome和vscode
  6. QQ被盗的自救、事故分析
  7. 了解一下Windows Cracker
  8. 帝国CMS7.2重置后台密码
  9. cmake导入so库_使用CMake引入第三方so库及头文件
  10. js当前日期倒推,向前倒推或往后推算