iOS CollectionView 列表&网格之间切换(带动画)

原文地址:https://www.hlzhy.com/?p=57

前言:

最近在写一个列表界面,这个列表能够在列表和网格之间切换,这种需求算是比较常见的。本以为想我们是站在大牛的肩膀上编程,就去找了下度娘和谷哥,但是并没有找到我想要的(找到的都是不带动画的切换)。既然做不了VC战士,那就自己动手丰衣足食。在我看来,所有的视图变化都应该尽量带个简单的过渡动画,当然,过度使用华丽的动画效果也会造成用户的审美疲劳。“动画有风险,使用需谨慎”。

依稀记得以前面试的时候被面试官问过这个问题,并被告知CollectionView自带有列表和网格之间切换并且带动画的API。最终找到如下方法:

/**
Summary
Changes the collection view’s layout and optionally animates the change.

Discussion
This method makes the layout change without further interaction from the user. If you choose to animate the layout change, the animation timing and parameters are controlled by the collection view.
*/
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated; // transition from one layout to another

最终效果(切换动画&动画慢放).gif

实现:

UIViewController.m

一、初始化UICollectionView

在当前控制器准备一个BOOLisList,用来记录当前选择的是列表还是网格,准备两个UICollectionViewFlowLayout对应列表和网格的布局,设置一个NOTIFIC_N_NAME宏,将此宏作为NotificationName,稍后将以通知的方式通知Cell改变布局。并且初始化UICollectionView。

@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, strong) UICollectionView *myCollectionView;

@property (nonatomic, assign) BOOL isList;
@property (nonatomic, strong) UICollectionViewFlowLayout *gridLayout;
@property (nonatomic, strong) UICollectionViewFlowLayout *listLayout;
@end
#define NOTIFIC_N_NAME @"ViewController_changeList"
@implementation ViewController
-(UICollectionViewFlowLayout *)gridLayout{
    if (!_gridLayout) {
        _gridLayout = [[UICollectionViewFlowLayout alloc] init];
        CGFloat width = (self.view.frame.size.width - 5) * 0.5;
        _gridLayout.itemSize = CGSizeMake(width, 200 + width);
        _gridLayout.minimumLineSpacing = 5;
        _gridLayout.minimumInteritemSpacing = 5;
        _gridLayout.sectionInset = UIEdgeInsetsZero;
    }
    return _gridLayout;
}
-(UICollectionViewFlowLayout *)listLayout{
    if (!_listLayout) {
        _listLayout = [[UICollectionViewFlowLayout alloc] init];
        _listLayout.itemSize = CGSizeMake(self.view.frame.size.width, 190);
        _listLayout.minimumLineSpacing = 0.5;
        _listLayout.sectionInset = UIEdgeInsetsZero;
    }
    return _listLayout;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    _myCollectionView = [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:self.gridLayout];
    _myCollectionView.showsVerticalScrollIndicator = NO;
    _myCollectionView.backgroundColor = [UIColor grayColor];
    _myCollectionView.delegate = self;
    _myCollectionView.dataSource = self;
    [self.view addSubview:_myCollectionView];
    [self.myCollectionView registerClass:[HYChangeableCell class] forCellWithReuseIdentifier:@"HYChangeableCell"];
    //......
}
二、实现UICollectionViewDataSource

创建UICollectionViewCell,给cell.isList赋值, 告诉Cell当前状态,给cell.notificationName赋值,用以接收切换通知。

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    HYChangeableCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"HYChangeableCell" forIndexPath:indexPath];
    cell.isList = _isList;
    cell.notificationName = NOTIFIC_N_NAME;
    return cell;
}
三、点击切换按钮

通过setCollectionViewLayout:animated:方法重新为CollectionView布局,并将animated设为YES。但是仅仅这样是不够的,因为这样并不会触发cellForItemAtIndexPath方法。我们还需向Cell发送通知告诉它“你需要改变布局了”。

-(void)changeListButtonClick{
    _isList = !_isList;
    if (_isList) {
        [self.myCollectionView setCollectionViewLayout:self.listLayout animated:YES];
    }else{
        [self.myCollectionView setCollectionViewLayout:self.gridLayout animated:YES];
    }
    //[self.myCollectionView reloadData];
    [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFIC_N_NAME object:@(_isList)];
}

UICollectionViewCell.m

基本布局代码这里就不贴上来了,需要的请在文章最后自行下载Demo查看。
!注意:因为这里使用的是UIView动画,因为UIView动画并不会根据我们肉眼所看到的动画效果过程中来动态改变宽高,在动画开始时其宽高就已经是结束状态时的宽高。所以用Masonry给子视图布局时,约束对象尽可能的避免Cell的右边和底边。否则动画将会出现异常,如下图的TitleLabel,我们能看到在切换时title宽度是直接变短的,也造成其它Label以它为约束对象时动画异常(下面红色字体的Label,切换时会往下移位)。

title约束为右边时动画慢放.gif

一、重写layoutSubviews

通过重写layoutSubviews方法,将[super layoutSubviews]写进UIView动画中,使Cell的切换过渡动画更平滑。

-(void)layoutSubviews{
    [UIView animateWithDuration:0.3 animations:^{
        [super layoutSubviews];
    }];
}
二、重写setNotificationName

重写setNotificationName方法并注册观察者。实现通知方法,将通知传来的值赋值给isList
最后记得移除观察者!

-(void)setNotificationName:(NSString *)notificationName{
    if ([_notificationName isEqualToString:notificationName]) return;
    _notificationName = notificationName;
    //注册通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(isListChange:) name:_notificationName object:nil];
}

-(void)isListChange:(NSNotification *)noti{
    BOOL isList = [[noti object] boolValue];
    [self setIsList:isList];
}

-(void)dealloc{
    //移除观察者
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
三、重写setIsList

重写setIsList方法,通过判断isList值改变子视图的布局。
代码较多,详细代码请下载Demo查看。

  • 此方法内 接收到通知进入时cell的frame并不准确,此时如果需要用到self.width,则需要自行计算,例如:
-(void)setIsList:(BOOL)isList{
    if (_isList == isList) return;
    _isList = isList;
    CGFloat width = _isList ? SCREEN_WIDTH : (SCREEN_WIDTH - 5) * 0.5;
    if (_isList) {
       //......
    }else{
       //......
    }
    //......
  • 如使用Masonry
    当布局相对简单时,约束使用mas_updateConstraints进行更新即可。当布局比较复杂,约束涉及到某控件宽,而这控件宽又是不固定的时候,可以考虑使用mas_remakeConstraints重做约束。

  • 约束都设置完成后,最后调用UIView动画更新约束。如果有用frame设置的,也将设置frame代码写在UIView动画内。
    !注意:如有用masonry约束关联了 用frame设置的视图,则此处需要把frame设置的视图写在前面。

-(void)setIsList:(BOOL)isList{
    //......
    [UIView animateWithDuration:0.3f animations:^{
        self.label3.frame = frame3;
        self.label4.frame = frame4;
        
        [self.contentView layoutIfNeeded];
    }];
}

Demo:

HYChangeableCollection

-END-
如果此文章对你有帮助,希望给个❤️。有什么问题欢迎在评论区探讨。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,067评论 1 32
  • 重点参考链接: View Programming Guide for iOS https://developer....
    Kevin_Junbaozi阅读 4,377评论 0 15
  • 阳光倾窗破寒凉 拂尘焚香净纤玉 风暖勾勒光影错 杯盏浅尝齿醇香 ——【午后时光•茶】明月2018.1.30
    明月姐阅读 313评论 0 0
  • 国外的月亮似乎就是更圆,至少对于目前的中国设计师来说。这不是崇洋媚外,我们必须认清我们中国目前的设计现状,与世界才...
    茂趣创意阅读 1,206评论 0 10
  • 三个大坑 1、莫名奇妙的凑热闹:并没有这种情况。 2、火急火燎的随大流:并没有这种情况。 3、操碎了别人的心肝:并...
    用户高卢总督阅读 157评论 0 0