一年前我在业余的时间做了一个课程表的界面,过程中基本上也很顺利的,近期由于一个校园项目的需要,所以就对其简单封装成了控件用在了项目中,但是在真正的项目中发现了很多当初没有考虑到的问题,所以在此将整个项目的历程记录一下,本篇主要以实例为主介绍课表的实现原理,基本不会涉及到代码,如果你有一定的Android基础,很容易根据原理实现一个自己的课表控件。
本篇博文主要讲解一下课表的实现原理,本项目已开源,地址如下:
在讲解实现原理前先要思考以下几个问题:
课表的布局方式用什么?
课程实体的属性有哪些?
如何动态添加课程?
点击事件如何处理?
重叠课程与交叉课程如何处理?
周次切换实现以及提高效率?
方法2:可以尝试复用布局,在切换布局的时候不清除布局,而是使用之前的布局,仅仅修改该布局的背景颜色与文本,这种方法效率非常高,但是存在限制。因为每个课程所占的高度以及位置都是根据课程的属性计算出来的,所以这种方法要求数据源不发生变化或者数据源的变化很小,即数据源删除某课程后对原有布局不造成影响或者添加某课程后可以与现有布局中的某块对应。
方法3:尝试两种方法结合,通过对原布局与新数据做对比,如果发现需要更新布局,那么就从当前布局开始清空当日布局,如果发现布局不需要变动,则只更新现有布局的背景颜色与文本。本方法的平均效率介于方法1与方法2之间,最低的效率等于方法1的效率,最高的效率等于方法2的效率。
经过上边的分析,是不是感觉有点似懂非懂~~,不要担心,下面将以实例以及图片为主进行讲解,保证能够看得懂
注:以下讨论时,默认已经将课程数据按照开始节次从小到大排列好了
相邻的课程会出现什么样的情况呢?
主要分为两种:正常情况、重叠或交叉
当某课程的开始节次大于等于上一个课程的结束节次时,那么这个情况就称为正常情况,这也是最普通的一种情况
当某课程的开始节次被包含在上一个课程所涵盖的节次范围内,那么这个情况就称为重叠或交叉情况
以下图片简单显示了这几种情况的场景,一个色块表示一个课程项,色块的高度表示课程的持续时间(step
),色块所在位置为课程的开始节次(start
),灰色表示非本周上,蓝色、粉色表示本周上
每周有7天,这里的布局使用7个水平排列的线性布局来承载周一至周日的课程,我们的任务就是将课程数据先按照周一至周日拆分为7个集合,然后将每天的课程数据填充到对应的线性布局中,由于这7块的操作是相同的,以下将以周一为例,演示在每种情况下如何将周一的课程添加到周一所在的线性布局上
每个课程所占的宽度是一定的,高度height=itemHeight* step + marTop * (step - 1)
,每个课程View所在的位置其实是由marginTop
值来确定的,marginTop
值与上个课程以及当前课程的start有关
正常情况
实例一: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “1周上”, “1”, “1”, “2”, “院楼205”],
[“数据库”, “李老师*”, “2周上”, “1”, “3”, “2”, “院楼202”],
]
假设当前周为第1周,本例中有两门课程,课程项的含义依次为课程名、教师、上课周次、星期、开始节次(start
)、步数(step
)、上课地点。(本文中所有实例中的课程项含义与此相同),解释一下第一个课程项的意思:它表示一个名为Linux
的课程,授课教师是刘老师,在第1周的星期一的第1节至第2节(1+2-1)上课
应该按照如下步骤进行构建;
LayoutInflate
将一个布局转化为View
,并设置其属性height=itemHeight* step + marTop * (step - 1)
marginTop
值:如果上个课程是null
,此时marginTop=0
,否则marginTop=(start - (preStart + preStep)) * (itemHeight + marTop) + marTop
,其中start
是当前课程的开始节次,preStart
是上个课程的开始节次,preStep
是上个课程的步数,itemHeight
是默认的1个单位的课程项的高度,marTop
是默认的课程项与上个课程项的上边距height
、marginTop
值,其中step
是步数本例中是这样构建的:
Linux
课程来说,marginTop=0
,height=itemHeight*2+marTop*1
,添加到布局中,设置高度、marginTop
,并添加到布局中,并设置preStart=start
,preStep=step
marginTop=(3-(1+2))*(itemHeight+marTop)+marTop=marTop
,height=itemHeight*2+marTop*1
marginTop
,并添加到布局中,并设置preStart=start
,preStep=step
非正常情况
当某课程的开始节次被包含在上一个课程所涵盖的节次范围内,那么这个情况就称为重叠或交叉情况
在构建的过程,需要首先判断是否发生了重叠或交叉,这个判断的方法非常的简单,如果start<=(preStart+preStep-1)
成立,那么就说明发生了重叠或交叉情况。
一旦发生了重叠或交叉,就应该判定哪个课程应该显示,还要把这张图再看一下:
start
越小,那么就显示哪个课程,另外课程忽略,如果start
相同,那么随机抽取一个显示即可情况a
实例二: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “1周上”, “1”, “1”, “4”, “院楼205”],
[“数据库”, “李老师*”, “1周上”, “1”, “1”, “2”, “院楼202”],
]
两个课程都是本周上,由于start
值相同,此时随便选择一个课程即可,则最后的结果为:显示Linux
课程,不显示数据库课程
实例三: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “1周上”, “1”, “1”, “4”, “院楼205”],
[“数据库”, “李老师*”, “1周上”, “1”, “3”, “2”, “院楼202”],
]
两个课程都是本周上,由于·start·值不同,此时应该显示Linux
课程,不显示数据库课程
实例四: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “1周上”, “1”, “1”, “2”, “院楼205”],
[“数据库”, “李老师*”, “1周上”, “1”, “2”, “2”, “院楼202”],
]
两个课程都是本周上,由于·start·值不同,此时应该显示Linux
课程,不显示数据库课程
情况b
实例五: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “2周上”, “1”, “1”, “2”, “院楼205”],
[“数据库”, “李老师*”, “1周上”, “1”, “2”, “2”, “院楼202”],
]
数据库课程本周上,此时应该显示数据库课程,不显示Linux
课程
实例六: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “2周上”, “1”, “1”, “4”, “院楼205”],
[“数据库”, “李老师*”, “1周上”, “1”, “3”, “4”, “院楼202”],
]
数据库课程本周上,此时应该显示数据库课程,不显示Linux
课程
情况c
可以类比情况b
情况d
实例七: 有两门课,它们是这样的:
[
[“Linux”, “刘老师*”, “2周上”, “1”, “1”, “4”, “院楼205”],
[“数据库”, “李老师*”, “2周上”, “1”, “3”, “2”, “院楼202”],
]
这种情况随便选取一个显示即可,另外一个忽略
如果不考虑其他的问题,切换周次实现起来非常简单,几行代码就解决了,一旦考虑多了,就感觉有点复杂了
先来重温一下切换周次的方法:
方法1
清除视图中所有布局,重新添加,这种方法容易实现但是效率很低,在切换过程中可以发现明显的卡顿
步骤如下:
方法2
复用布局,在切换布局的时候使用已有的布局,仅仅修改该布局的背景颜色与文本,这种方法效率非常高,但是存在限制。因为每个课程所占的高度以及位置都是根据课程的属性计算出来的,所以这种方法要求数据源不发生变化或者数据源的变化很小,即数据源删除某课程后对原有布局不造成影响或者添加某课程后可以与现有布局中的某块对应。
本方法的缺点在于一旦初始布局创建完成后,布局不会发生变化,如果数据集发生了变化,课程项的高度无法更新
步骤如下:
方法3
尝试两种方法结合,通过对原布局与新数据做对比,如果发现需要更新布局,那么就从当前布局开始清空当日布局,如果发现布局不需要变动,则只更新现有布局的背景颜色与文本,本方法的平均效率介于方法1与方法2之间
Tag
) obj
subject
总的来说,整个过程不算难,主要理解如何处理课程重叠和交叉的问题就可以了,本文主要讲了课表的实现原理,没有涉及到任何的代码,但是通过本文,你应该可以写出一个课表界面的轮廓了,剩下的细节你可以慢慢优化
有任何疑问,可以加我QQ:1193600556
,我也是新手,我博客里大部分的文章都是介绍我的开源项目的,这应该是我博客里第一篇技术分析的文章了,有任何写的不好的地方欢迎指正。
如果本文不理解,可以结合源码分析TimetableView,如果你想做个自己的课表控件但苦于没有课程数据的话,可以看看课程表API,这是我的一个开源项目-河南理工大学课程库,后台已部署在我的服务器上,可以直接使用
如果你喜欢这个课表控件TimetableView,点个start
就是对我最大的鼓励了,开源项目推广前期真的是无比艰难-_-,现在它才10个左右的start
,需要你的火力支援…