因为项目中有课程表的相关模块,第一时间想到用UICollectionView。然而后期的需求越来越复杂,每个格子需要展示的内容越来越多,所以不得不寻找合适的解决方案。最后发现自定义UICollectionViewLayout可以实现我的需求。

先放效果图:

课程表.gif

需要知道的:

1. UICollectionViewLayoutAttributes

首先我们要明白,它是我们自定义layout非常关键的一个类,它包含了cell、header、footer等视图的边框,中心点,大小,形状,透明度,层次关系和是否隐藏等信息,其中frame是我们需要用到的。

2. 自定义UICollectionViewLayout需要重载的方法:

// 返回collectionView的内容的尺寸

- (CGSize)collectionViewContentSize;

// 返回rect中的所有的元素的布局属性

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;

// 返回对应于indexPath的位置的cell的布局属性

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

// 当边界发生改变时,是否应该刷新布局。

// return YES 表示在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;

// 以上四个方法是我们需要用到的,重载他们就行。下面两个方法暂时不说。

- (UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(NSString _)kind atIndexPath:(NSIndexPath *)indexPath;

- (UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath;

3. 生命周期

当一个UICollectionViewLayout被创建后,会有一系列方法被系统自动调用;

// 首先,我们在这个方法中重新计算Attributes的各个属性

-(void)prepareLayout;

// 然后,我们从这个方法获取cell的布局属性

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;

// 另外,我们可以调用这个方法立即刷新layout

- (void)invalidateLayout;

具体实现:

1. 首先,我们需要一个变量来记录被选中列

// 被选中列

@property (nonatomic, assign) IBInspectable NSInteger extendIndex;

2. 接下来,我们根据UICollectionView的section数、每个section的item数确定全部cell的itemSize,并保存他们的集合。这里的重点是需要区分第一行,第一列还有选中列的itemSize。

// 计算得到表格视图itemSize的集合

- (void)calculateItemsSize {

NSMutableArray *sectionArray = [@[] mutableCopy];

for (NSInteger section = 0; section < [self.collectionView numberOfSections]; section ++) {

NSMutableArray *itemArray = [@[] mutableCopy];

for (NSUInteger index = 0; index < self.rows; index++) {

if (self.itemsSize.count <= index) {

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:section];

CGSize itemSize = [self sizeForItemWithColumnIndexPath:indexPath];

NSValue *itemSizeValue = [NSValue valueWithCGSize:itemSize];

[itemArray addObject:itemSizeValue];

}

}

[sectionArray addObject:itemArray];

}

self.itemsSize = sectionArray;

}

// 获取对应indexPath的itemSize

static CGFloat const extendWidth = 100.0;

- (CGSize)sizeForItemWithColumnIndexPath:(NSIndexPath *)indexPath {

CGSize size = CGSizeMake(60, 100);//预设cell的size

//根据表格的特性,调整不同情况下cell的宽高

size.width = indexPath.section ? size.width : 40;

size.height = indexPath.item ? size.height : 50;

size.width = (indexPath.section == self.extendIndex && indexPath.section > 0) ? extendWidth : size.width;

return size;

}

3. 根据itemSize重载对应attributes

UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

// 确保第一行与第一列永远顶在NavigationBar的下方跟屏幕的左边

if (section == 0 && index == 0) {

attributes.zIndex = 1024;

} else if (section == 0 || index == 0) {

attributes.zIndex = 1023;

}

if (section == 0) { //第一列

CGRect frame = attributes.frame;

frame.origin.x = self.collectionView.contentOffset.x;

attributes.frame = frame;

}

if (index == 0) { // 第一行

CGRect frame = attributes.frame;

frame.origin.y = self.collectionView.contentOffset.y;

attributes.frame = frame;

}

PS:这里重点在于,collectionView横向或者竖向滚动时第一列与第一行的cell不能跟着移动位置,而通过self.collectionView.contentOffset我们可以知道collectionView分别在水平、竖直方向上的位移,那么有两种情况:

水平滚动:我们将collectionView在x轴上的偏移量赋给第一列cell的frame.origin.x,即可造成第一列没有滑动的视觉假象。

竖直滚动:原理同上。

for (int section = 0; section < [self.collectionView numberOfSections]; section++) {

NSMutableArray *sectionAttributes = [@[] mutableCopy];

for (NSUInteger index = 0; index < self.rows; index++) {

CGSize itemSize = [self.itemsSize[section][index] CGSizeValue];

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];

UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

attributes.frame = CGRectIntegral(CGRectMake(xOffset, yOffset, itemSize.width, itemSize.height));

yOffset = yOffset+itemSize.height;

column++;

if (column == self.rows) {

if (yOffset > contentHeight) {

contentHeight = yOffset;

}

// Reset values

column = 0;

xOffset += itemSize.width;

yOffset = 0;

}

}

}

这里我们通过遍历cell得到attributes,继而通过累加算出collectionView的contentSize:

UICollectionViewLayoutAttributes *attributes = [[self.itemAttributes lastObject] lastObject];

contentWidth = attributes.frame.origin.x + attributes.frame.size.width;

self.contentSize = CGSizeMake(contentWidth, contentHeight);

4. 之后,就是去重载UICollectionViewLayout的那几个需要用到的方法了:

#pragma mark - Overwrite

//返回collectionView的内容的尺寸

- (CGSize)collectionViewContentSize {

return self.contentSize;

}

// 返回对应于indexPath的位置的cell的布局属性

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {

return self.itemAttributes[indexPath.section][indexPath.row];

}

// 返回rect中的所有的元素的布局属性

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

NSMutableArray *attributes = [@[] mutableCopy];

for (NSArray *section in self.itemAttributes) {

[attributes addObjectsFromArray:[section filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) {

return CGRectIntersectsRect(rect, [evaluatedObject frame]);

}]]];

}

return attributes;

}

// 当边界发生改变时,是否应该刷新布局

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {

return YES; // 在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息

}

5. 最后,在-prepareLayout方法中清空布局数组、重新计算itemSize、刷新Attributes

- (void)prepareLayout {

[self clearLayoutArray];

[self calculateItemsSize];

[self updateItemsAttributes];

}

到这里也就大功告成了,放上

Tips:

通过IBInspectable关键字可申明该属性可在Storyboard中设置

// 被选中列

@property (nonatomic, assign) IBInspectable NSInteger extendIndex;

image.png

参考资料

android 课程表 ui,UICollectionViewLayout实现课程表布局相关推荐

  1. Android抓取正方系统课程——实现自己的课程表

    Android抓取正方系统课程--实现自己的课程表 上一篇博客讲解了如何使用http协议模拟登陆正方系统,今天继续实现如何抓取课程表并显示在Android界面上,效果如图: 由于偷懒,在界面上没下太多 ...

  2. android 自定义课程表,Android课程表界面布局实现代码

    前言 Android课程表布局实现 我是个菜鸟,文章供参考 示例 图1: 图2: 布局分析 该界面主要可分为三部分: 1.显示年份及周数部分 2.显示周一到周日 3.课程显示部分 实现步骤 1.首先整 ...

  3. android jsoup 课程表,使用jsoup爬取数据实现android课程表

    说明:只是爬虫的一个实现案例,所以没有多做功能,只做了登录跟课表功能,课表有修改周次,单击课程显示课程详细信息等功能. 开发平台:Android Studio 界面 使用TimetableView a ...

  4. Android课程表App

    最近写了个简单的Android 课程表App,我是个初学者,这个App里使用了: Android内置的SQLite数据库储存课程数据. 课程的视图用CardView卡片视图. 课程的View是动态加入 ...

  5. android课程表控件、悬浮窗、Todo应用、MVP框架、Kotlin完整项目源码

    Android精选源码 Android游戏2048 MVP Kotlin项目(RxJava+Rerotfit+OkHttp+Glide) Android基于自定义Span的富文本编辑器 android ...

  6. Android课程表的设计开发

    Android课程表的设计开发 下载链接 鉴于很多人需要源码,这里给下代码. 下载地址(需要5积分,支持下(积累点积分...),没有积分的直接留言邮箱,我发给你或者找其他已经发过的人要下) 没积分的直 ...

  7. ​第一本 Compose 图书上市,联想大咖教你学会 Android 全新 UI 编程

    朱江 | 现任联想(北京)有限公司 Android 开发工程师,从事 Android 开发工作多年,有丰富的项目经验,负责和参与开发过多款移动应用程序,同时还是多个开源项目的作者.2017 年开始在 ...

  8. android ui 最新教程,Android更新UI的五种方式,androidui五种

    Android更新UI的五种方式,androidui五种handler.post activity.runOnUiThread view.post handler+Thread AsyncTask 例 ...

  9. android的UI开发工程师指引

    不管是MFC,还是linux,还是android,UI开发都是如下两大核心机制: 第一个是消息循环,第二个是界面组织结构. 围绕着这些,衍生出来的OpenGL,SurfaceView,SurfaceF ...

最新文章

  1. 计算两个日期之间的年数
  2. js获取浏览器高和宽的基本信息:屏幕信息
  3. Python 二分查找算法
  4. 网易实践|千万级在线直播弹幕方案
  5. 【Tools】VMware虚拟机三种网络模式详解和操作
  6. css实现透明度(兼容IE6、火狐等)
  7. C#软件开发实例.私人订制自己的屏幕截图工具(十)在截图中包括鼠标指针形状...
  8. CentOS 7 各个版本的区别
  9. Python爬虫编程常见问题解决方法
  10. signal、kill、fork
  11. 基于RSA解题时yafu的使用
  12. 如何新浪微博html5,新浪微博接入Html5游戏 注重轻量碎片化
  13. 新视野大学英语读写2 78单元翻译
  14. NLP算法-词性标注
  15. flutter 学习之项目一
  16. IO多路复用和epoll反应堆
  17. 原画师惊呆:这个爆火AI真把梦境画成现实了!下载APP人人可用
  18. COM(Componet Object Model_组件对象模型)技术概述
  19. Facebook要做的事,这家公司4年前就在做了
  20. RaspberryPi+OneNET MQTT方式 数据上传和命令下发

热门文章

  1. SCS【25】单细胞细胞间通信第一部分细胞通讯可视化(CellChat)
  2. 给网站添加建站时长的js代码
  3. python实现股票自动交易_利用python3.5 +TK 开发股票自动交易伴侣
  4. Android动画Animator家族使用指南
  5. 前缀表达式的计算机求值
  6. 黑盒测试方法—因果图法
  7. AreEngine IGeometry转WKT,WKB
  8. 如何申请专利,申请专利的步骤和费用
  9. 30+宝妈北漂4年,从行政成功转行软件测试,在地铁站外喜极而泣......
  10. JavaScript 获取随机数