转载于做者: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
复制代码
在当前控制器准备一个BOOL
值isList
,用来记录当前选择的是列表仍是网格,准备两个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"];
//......
}
复制代码
建立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;
}
复制代码
经过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)];
}
复制代码
基本布局代码这里就不贴上来了,须要的请在文章最后自行下载Demo查看。 !注意:由于这里使用的是UIView动画,由于UIView动画并不会根据咱们肉眼所看到的动画效果过程当中来动态改变宽高,在动画开始时其宽高就已是结束状态时的宽高。因此用Masonry给子视图布局时,约束对象尽量的避免Cell的右边和底边。不然动画将会出现异常,以下图的TitleLabel,咱们能看到在切换时title宽度是直接变短的,也形成其它Label以它为约束对象时动画异常(下面红色字体的Label,切换时会往下移位)。 布局
经过重写layoutSubviews方法,将[super layoutSubviews]
写进UIView动画中,使Cell的切换过渡动画更平滑。post
-(void)layoutSubviews{
[UIView animateWithDuration:0.3 animations:^{
[super layoutSubviews];
}];
}
复制代码
重写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方法,经过判断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{
//......
}
//......
复制代码
如使用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];
}];
}
复制代码
小编这呢,给你们推荐一个优秀的iOS交流平台,平台里的伙伴们都是很是优秀的iOS开发人员,咱们专一于技术的分享与技巧的交流,你们能够在平台上讨论技术,交流学习。欢迎你们的加入(想要加入的可加小编微信15673450590)。
-END- 若是此文章对你有帮助,但愿给个❤️。有什么问题欢迎在评论区探讨。