iOS CollectionView 列表&網格之間切換(帶動畫)

轉載於做者:Hank_Zhong 地址:www.hlzhy.com/?p=57git

前言:

最近在寫一個列表界面,這個列表可以在列表和網格之間切換,這種需求算是比較常見的。本覺得想咱們是站在大牛的肩膀上編程,就去找了下度娘和谷哥,可是並無找到我想要的(找到的都是不帶動畫的切換)。既然作不了VC戰士,那就本身動手豐衣足食。在我看來,全部的視圖變化都應該儘可能帶個簡單的過渡動畫,固然,過分使用華麗的動畫效果也會形成用戶的審美疲勞。「動畫有風險,使用需謹慎」。github

依稀記得之前面試的時候被面試官問過這個問題,並被告知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

複製代碼

實現:

UIViewController.m

1、初始化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"];
    //......
}
複製代碼

2、實現UICollectionViewDataSource

建立UICollectionViewCell,給cell.isList賦值, 告訴Cell當前狀態,給cell.notificationName賦值,用以接收切換通知。bash

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    HYChangeableCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"HYChangeableCell" forIndexPath:indexPath];
    cell.isList = _isList;
    cell.notificationName = NOTIFIC_N_NAME;
    return cell;
}
複製代碼

3、點擊切換按鈕

經過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,切換時會往下移位)。 佈局

1、重寫layoutSubviews

經過重寫layoutSubviews方法,將[super layoutSubviews]寫進UIView動畫中,使Cell的切換過渡動畫更平滑。post

-(void)layoutSubviews{
    [UIView animateWithDuration:0.3 animations:^{
        [super layoutSubviews];
    }];
}
複製代碼

2、重寫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];
}
複製代碼

3、重寫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

小編這呢,給你們推薦一個優秀的iOS交流平臺,平臺裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在平臺上討論技術,交流學習。歡迎你們的加入(想要加入的可加小編微信15673450590)。

-END- 若是此文章對你有幫助,但願給個❤️。有什麼問題歡迎在評論區探討。

相關文章
相關標籤/搜索