原文描述了一个新项目中UI设计的卡片式布局实现过程,目的是展示用户对电影的感悟。由于以前类似布局的代码丢失,作者决定自行整理代码,方便复用。具体实现包括:使用UICollectionView实现左右滑动布局,当滑动到最后一张时提示用户暂无更多;实现无限轮播功能,包括图片数量大于三张、等于两张、等于一张时的不同处理逻辑;以及通过自定义UICollectionViewLayout来调整cell的缩放比例,实现中心卡片放大效果,并给出了关键代码示例。此外,还处理了图片滑动至中心位置时,最后一张和第一张图片的特殊情况。
最近写一个新项目时,UI设计了一种常规的卡片式的布局,来显示用户对电影的感悟,以前也写过这类布局,代码早已不知去向,每次遇到都是网上搜索,修改,每次都是这么重复,这次不如自己把这部分代码,功能整理出来,方便下次自己使用。
一.业务需求
二.实现思路
1.用UICollectionView来实现左右滑动的页面布局。
2.滑动最后一张时,提示用户暂无更多。(设计要求)
3.实现无限轮播。
4.时间间隔内自动播放。
三.关键代码
设置cell的缩放比例,实现中心卡片的位置。
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *arr = [self getCopyOfAttributes:[super layoutAttributesForElementsInRect:rect]]; //屏幕中线 CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.bounds.size.width/2.0f; //刷新cell缩放 for (UICollectionViewLayoutAttributes *attributes in arr) { CGFloat distance = fabs(attributes.center.x - centerX); //移动的距离和屏幕宽度的的比例 CGFloat apartScale = distance/self.collectionView.bounds.size.width; //把卡片移动范围固定到 -π/4到 +π/4这一个范围内 CGFloat scale = fabs(cos(apartScale * M_PI/4)); //设置cell的缩放 按照余弦函数曲线 越居中越趋近于1 attributes.transform = CGAffineTransformMakeScale(1.0, scale); } return arr; }
滑动最后一张时,提示用户暂无更多。
- (void)setModels:(NSArray *)models{ if (models.count == 0) { return; } NSMutableArray *modelsM = @[].mutableCopy; [modelsM addObjectsFromArray:models]; ShowBannerModel *first = [[ShowBannerModel alloc]init];//添加一条新数据,设置空白页 [modelsM addObject:first]; }
3.实现无限轮播,实现无限轮播时就需要考虑多种情况,数据图片只有一张,两张,大于3张时的情况。
- (void)setModels:(NSArray *)models{ if (models.count == 0) { return; } //处理模型 实现无限滚动 NSMutableArray *modelsM = @[].mutableCopy; [modelsM addObjectsFromArray:models]; if (modelsM.count >= 3) { if(_isRepeat){ ShowBannerModel *first = modelsM.firstObject; ShowBannerModel *seconed = modelsM[1]; ShowBannerModel *last = modelsM.lastObject; ShowBannerModel *lastTwo = modelsM[models.count - 2]; [modelsM insertObject:last atIndex:0]; [modelsM insertObject:lastTwo atIndex:0]; [modelsM addObject:first]; [modelsM addObject:seconed]; } }else if (modelsM.count == 2) { if(_isRepeat){ ShowBannerModel *first = modelsM.firstObject; ShowBannerModel *seconed = modelsM.lastObject; ShowBannerModel *last = modelsM.lastObject; ShowBannerModel *lastTwo = modelsM.firstObject; [modelsM insertObject:last atIndex:0]; [modelsM insertObject:lastTwo atIndex:0]; [modelsM addObject:first]; [modelsM addObject:seconed]; } } }else if (modelsM.count == 1) { _currentIndex = 0; if(_isRepeat){ ShowBannerModel *first = [[ShowBannerModel alloc]init]; ShowBannerModel *seconed = modelsM.firstObject; ShowBannerModel *last = modelsM.lastObject; ShowBannerModel *lastTwo = models.lastObject; [modelsM insertObject:last atIndex:0]; [modelsM insertObject:lastTwo atIndex:0]; [modelsM addObject:first]; [modelsM addObject:seconed]; } } _models = modelsM; //设置初始位置 if (models.count > 0) { [self.collection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; if (self.delegate && [self.delegate respondsToSelector:@selector(ShowBannerView:scrollAt:)]) { [self.delegate ShowBannerView:self scrollAt:_currentIndex]; } [self.collection reloadData]; }
图片滑动中心显示时,也要考虑最后一张和第一张图片的情况
if(_isRepeat){ //如果是最后一张图 if (_currentIndex == self.models.count - 1 ){ _currentIndex = 2; [self.collection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; return; } //第一张图 else if (_currentIndex == 1) { _currentIndex = self.models.count - 3; [self.collection scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:_currentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; return; } }
4.设置时间间隔内自动播放。
#pragma mark 设置定时器时间 - (void)setTimeInterval:(NSTimeInterval)timeInterval { _timeInterval = timeInterval; if(_isAutomatic){ [self startTimer]; } } - (void)startTimer { //如果只有一张图片,则直接返回,不开启定时器 if (_models.count <= 1) return; //如果定时器已开启,先停止再重新开启 if (self.timer){ [self stopTimer]; } NSTimeInterval timeInterval = _timeInterval ? (_timeInterval >= 1 ?: 1) : DefaultTime; self.timer = [NSTimer timerWithTimeInterval:timeInterval target:self selector:@selector(nextPage) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; } - (void)stopTimer { [self.timer invalidate]; self.timer = nil; }
以上实现的思路和关键代码,具体demo :https://github.com/dt8888/LeftAndRightScrollerView
四.如何引用demo的ShowBannerView
1.导入类 #import "ShowBannerView.h"
2.使用代码示例
CGFloat width = self.view.bounds.size.width; _banner = [[ShowBannerView alloc] initWithFrame:CGRectMake(0, bgView.bottom+30, width, 520)]; _banner.delegate = self; _banner.isRepeat = YES;//是否无限录播 _banner.isAutomatic = YES;//是否定时自动显示 [_scrollView addSubview:_banner];
3.设置代理 _banner.delegate = self; 遵守<ShowBannerViewDelegate>协议,并实现代理的方法
//滑动到某页面 - (void)ShowBannerView:(ShowBannerView *)bannerView scrollAt:(NSInteger)index{} //点击某一个卡片的方法 - (void)ShowBannerView:(ShowBannerView *)bannerView didSelectedAt:(NSInteger)index{}
4.最终实现 的效果图
本篇独发金蝶云社区
推荐阅读