原文地址:https://www.hlzhy.com/?p=57
前言:
最近在寫一個(gè)列表界面够傍,這個(gè)列表能夠在列表和網(wǎng)格之間切換,這種需求算是比較常見的挠铲。本以為想我們是站在大牛的肩膀上編程冕屯,就去找了下度娘和谷哥,但是并沒有找到我想要的(找到的都是不帶動(dòng)畫的切換)拂苹。既然做不了VC戰(zhàn)士安聘,那就自己動(dòng)手豐衣足食。在我看來瓢棒,所有的視圖變化都應(yīng)該盡量帶個(gè)簡(jiǎn)單的過渡動(dòng)畫浴韭,當(dāng)然,過度使用華麗的動(dòng)畫效果也會(huì)造成用戶的審美疲勞音羞〈呀埃“動(dòng)畫有風(fēng)險(xiǎn),使用需謹(jǐn)慎”嗅绰。
依稀記得以前面試的時(shí)候被面試官問過這個(gè)問題,并被告知CollectionView自帶有列表和網(wǎng)格之間切換并且?guī)?dòng)畫的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
實(shí)現(xiàn):
UIViewController.m
一窘面、初始化UICollectionView
在當(dāng)前控制器準(zhǔn)備一個(gè)BOOL
值isList
,用來記錄當(dāng)前選擇的是列表還是網(wǎng)格叽躯,準(zhǔn)備兩個(gè)UICollectionViewFlowLayout
對(duì)應(yīng)列表和網(wǎng)格的布局财边,設(shè)置一個(gè)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"];
//......
}
二、實(shí)現(xiàn)UICollectionViewDataSource
創(chuàng)建UICollectionViewCell憨募,給cell.isList
賦值紧索, 告訴Cell當(dāng)前狀態(tài),給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;
}
三、點(diǎn)擊切換按鈕
通過setCollectionViewLayout:animated:
方法重新為CollectionView布局尾膊,并將animated
設(shè)為YES媳危。但是僅僅這樣是不夠的,因?yàn)檫@樣并不會(huì)觸發(fā)cellForItemAtIndexPath
方法冈敛。我們還需向Cell發(fā)送通知告訴它“你需要改變布局了”待笑。
-(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
基本布局代碼這里就不貼上來了,需要的請(qǐng)?jiān)谖恼伦詈笞孕邢螺dDemo查看抓谴。
暮蹂!注意:因?yàn)檫@里使用的是UIView動(dòng)畫,因?yàn)閁IView動(dòng)畫并不會(huì)根據(jù)我們?nèi)庋鬯吹降膭?dòng)畫效果過程中來動(dòng)態(tài)改變寬高齐邦,在動(dòng)畫開始時(shí)其寬高就已經(jīng)是結(jié)束狀態(tài)時(shí)的寬高椎侠。所以用Masonry給子視圖布局時(shí),約束對(duì)象盡可能的避免Cell的右邊和底邊措拇。否則動(dòng)畫將會(huì)出現(xiàn)異常我纪,如下圖的TitleLabel,我們能看到在切換時(shí)title寬度是直接變短的丐吓,也造成其它Label以它為約束對(duì)象時(shí)動(dòng)畫異常(下面紅色字體的Label浅悉,切換時(shí)會(huì)往下移位)。
一券犁、重寫layoutSubviews
通過重寫layoutSubviews方法术健,將[super layoutSubviews]
寫進(jìn)UIView動(dòng)畫中,使Cell的切換過渡動(dòng)畫更平滑粘衬。
-(void)layoutSubviews{
[UIView animateWithDuration:0.3 animations:^{
[super layoutSubviews];
}];
}
二荞估、重寫setNotificationName
重寫setNotificationName方法并注冊(cè)觀察者。實(shí)現(xiàn)通知方法稚新,將通知傳來的值賦值給isList
勘伺。
最后記得移除觀察者!
-(void)setNotificationName:(NSString *)notificationName{
if ([_notificationName isEqualToString:notificationName]) return;
_notificationName = notificationName;
//注冊(cè)通知
[[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
值改變子視圖的布局。
代碼較多屯阀,詳細(xì)代碼請(qǐng)下載Demo查看缅帘。
- 此方法內(nèi) 接收到通知進(jìn)入時(shí)cell的frame并不準(zhǔn)確轴术,此時(shí)如果需要用到self.width,則需要自行計(jì)算钦无,例如:
-(void)setIsList:(BOOL)isList{
if (_isList == isList) return;
_isList = isList;
CGFloat width = _isList ? SCREEN_WIDTH : (SCREEN_WIDTH - 5) * 0.5;
if (_isList) {
//......
}else{
//......
}
//......
如使用Masonry
當(dāng)布局相對(duì)簡(jiǎn)單時(shí)逗栽,約束使用mas_updateConstraints進(jìn)行更新即可。當(dāng)布局比較復(fù)雜铃诬,約束涉及到某控件寬祭陷,而這控件寬又是不固定的時(shí)候,可以考慮使用mas_remakeConstraints重做約束趣席。約束都設(shè)置完成后兵志,最后調(diào)用UIView動(dòng)畫更新約束。如果有用frame設(shè)置的宣肚,也將設(shè)置frame代碼寫在UIView動(dòng)畫內(nèi)想罕。
!注意:如有用masonry約束關(guān)聯(lián)了 用frame設(shè)置的視圖霉涨,則此處需要把frame設(shè)置的視圖寫在前面按价。
-(void)setIsList:(BOOL)isList{
//......
[UIView animateWithDuration:0.3f animations:^{
self.label3.frame = frame3;
self.label4.frame = frame4;
[self.contentView layoutIfNeeded];
}];
}
Demo:
-END-
如果此文章對(duì)你有幫助,希望給個(gè)??笙瑟。有什么問題歡迎在評(píng)論區(qū)探討楼镐。