iOS开发程序员iOS 开发

iOS CollectionView 列表&网格之间切换

2019-01-03  本文已影响147人  Hank_Zhong

原文地址: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查看。

-(void)setIsList:(BOOL)isList{
    if (_isList == isList) return;
    _isList = isList;
    CGFloat width = _isList ? SCREEN_WIDTH : (SCREEN_WIDTH - 5) * 0.5;
    if (_isList) {
       //......
    }else{
       //......
    }
    //......
-(void)setIsList:(BOOL)isList{
    //......
    [UIView animateWithDuration:0.3f animations:^{
        self.label3.frame = frame3;
        self.label4.frame = frame4;
        
        [self.contentView layoutIfNeeded];
    }];
}

Demo:

HYChangeableCollection

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

上一篇下一篇

猜你喜欢

热点阅读