#前言:
项目中需要实现个类似浏览器中标签页管理的界面。由于大boss觉得界面卡所以需要改动。(其实不是卡,是滑动时动画不如多个的连贯导致的,但大boss有需求,就算没条件,创造条件也要上)
这次写的比较粗糙,望见谅。
- 想要实现的效果
- 实现之前的效果
实现以后的效果
因为以前就很想使用collectionView,这次x老大让我来做这个改动,心里那个激动啊。
废话不说,直接加入主题。
#布局对象
collectionView和tableView都是由data-source
和delegate
驱动的。但为什么collectionView会比tableView在对cell的布局更灵活呢?
因为collectionView将cell的位置、大小、外观委托给一个单独的布局对象(继承自collectionViewLayout这个基类)来管理。所以通过自定义一个布局对象,你能实现任何你能想象的到的布局(比如:瀑布流、水平缩放、杂志封面等)。
在iOS中有个可以直接拿过来使用的布局对象UICollectionViewFlowLayout(流式布局)。它通过一个接着一个的放置cell来建立自己的布局。通过设置滚动的方向、大小及cell之间的间距,flowLayout可以在单行或单列中布局cell。 在上图给出的效果图,我们可以直接定制flowLayout而不需要继承自collectionViewLayout这个基类,因为flowLayout以及能够满足我们的要求了。
#Cells和其他Views
为了能让我们做出任意的布局,collectionView拥有着和tableView类似的视图层级但比tableView更灵活的。像使用tableViw一样,你的主要内容显示在cell中,cell可以被任意分组到section中。 collectionView也有和tableView section中headerView和footView,但是用supplementaryView来表示header/foot,用法和tableview不一样,supplementaryView的数量和放置的位置完全由布局来控制。 除此之外,collectionView还有个decorationView。这个view完全就是一个装饰用的view。它并不从数据源获取内容。 supplementary和decoration必须是UICollectionResuableView的子类。 在布局中使用的视图类都需要在collectionView中注册(register),这样当data source从重用池中取出视图时,它才能重建新的实例。注册一般放到viewdidload中做。
如果你使用代码写的cell/supplementaryView使用下面的注册方法:
- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
如果使用的是xib弄的cell/supplementaryView使用下面的政策方法:
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;
#自定义布局
一般来说有2种类型的collectionView布局:
- 独立于内容的布局计算.像tableview货flowLayout这些情况就是这种布局。每个cell的位置和外观不是基于它显示的内容来计算的,所有cell的显示顺序却是基于内容的顺序。以flowlayout(tableView可以看出是flowlayout在竖直情况下的布局)作为例子,每个cell都是基于前一个cell的放置。布局对象不必访问实际数据来计算布局。
- 基于内容的布局计算.为了计算cell的显示位置和外观,布局对象需要直接访问collectionView的数据源。例如日历事件的视图就是这种类型的。在很多情况下,布局对象不仅需要取出当前可见cell的数据,还需要从所有记录中取出一些决定当前那些cell的可见数据。
如果有个依赖内容的布局,那你就不能使用自定义的uicollectionviewflowlayou,而需要写自定义的(继承自uicollectionViewLayout基类的)布局类。
#需要重写的方法
- (CGSize)collectionViewContentSize;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
很多blog说上面的6个方法都是必须被重写的但在我的代码中有几个没重写,也能正常使用。apple文档中这样子写道:every layout object should implement the following methods.
下面上布局的代码:其他的代码就不能上了(因为collectionView的最核心的部分就是layout这里的代码了)。待我有空了,就专门写个比较酷炫的demo上传到github上来和大家一起讨论研究。共同进步。
// WSPXBrowserTabsLayout.m
// UOne
//
// Created by MrChens on 15/12/1.
// Copyright © 2015年 chinanetcenter. All rights reserved.
//
#import "WSPXBrowserTabsLayout.h"
#import "WSPXBrowserTabsLayoutAttributes.h"
#define ACTIVE_DISTANCE 100 //cell中心点到屏幕中心点之间的间距
#define TRANSLATE_DISTANCE 100
#define ZOOM_FACTOR 0.25f//恢复成1时剩下的比率
#define SCALE_FACTOR 0.75f//相对于中间的cell的比率
#define FLOW_OFFSET 5
#define INACTIVE_GREY_VALUE 0.6f
#define kMrChenLog (1)
@interface WSPXBrowserTabsLayout ()
@property (nonatomic, strong) NSMutableArray *deleteIndexPaths;
@property (nonatomic, strong) NSMutableArray *insertIndexPaths;
@property (nonatomic, assign) CGFloat itemHeight;
@property (nonatomic, assign) CGFloat itemWidth;
@end
@implementation WSPXBrowserTabsLayout
/**< 从xib中加载 */
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_itemWidth = kScreenWidth * 0.6;
_itemHeight = kScreenHeight * 0.6;
self.itemSize = CGSizeMake(_itemWidth, _itemHeight);
self.minimumInteritemSpacing = 20;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.decelerationRate = 1;
}
return self;
}
/**< 从代码加载 */
-(id)init
{
self = [super init];
if (self) {
_itemWidth = kScreenWidth * 0.6;
_itemHeight = kScreenHeight * 0.6;
self.itemSize = CGSizeMake(_itemWidth, _itemHeight);
self.minimumInteritemSpacing = 20;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.decelerationRate = 1;
}
return self;
}
+ (Class)layoutAttributesClass {
return [WSPXBrowserTabsLayoutAttributes class];
}
-(void)prepareLayout {
[super prepareLayout];
self.collectionView.contentInset = UIEdgeInsetsMake(0, (CGRectGetWidth(self.collectionView.frame) - self.itemSize.width) / 2, 0, (CGRectGetWidth(self.collectionView.frame) - self.itemSize.width) / 2);
}
#pragma mark -
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes forVisibleRect:(CGRect)visibleRect{
if (attributes.representedElementKind) {
return;
}
//计算当前的cell中心距离屏幕的中心的位置
CGFloat distanceFromVisibleRectToItem = CGRectGetMidX(visibleRect) - attributes.center.x;
// NSLog(@"距离的位置:%lf", distanceFromVisibleRectToItem);
//计算距离与***的比例
CGFloat normalizedDistance = distanceFromVisibleRectToItem / ACTIVE_DISTANCE;
CGAffineTransform transforms =CGAffineTransformIdentity;
CGFloat maskAlpha = 0.0f;
//如果当前cell中心位置的到屏幕的中心位置小于某个值的话,设置移动的动画
if (fabs(distanceFromVisibleRectToItem) < ACTIVE_DISTANCE) {
// NSLog(@"移动的比例:%lf", normalizedDistance);
CGFloat scale = SCALE_FACTOR + ZOOM_FACTOR * (1 - ABS(normalizedDistance));
// NSLog(@"缩放的比例:%lf", scale);
transforms = CGAffineTransformScale(transforms, scale, scale);
attributes.zIndex = 0;
maskAlpha = INACTIVE_GREY_VALUE + (1- ABS(normalizedDistance))* (-INACTIVE_GREY_VALUE);
//如果大于某个值的话,设置普通的动画
} else {
transforms = CGAffineTransformScale(transforms, SCALE_FACTOR, SCALE_FACTOR);
attributes.zIndex = 1;//值越小则越在顶部
maskAlpha = INACTIVE_GREY_VALUE;
}
attributes.transform = transforms;
[(WSPXBrowserTabsLayoutAttributes *)attributes setMaskingValue:maskAlpha];
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *layoutAttributesArray = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, CGRectGetWidth(self.collectionView.bounds), CGRectGetHeight(self.collectionView.bounds));
for (UICollectionViewLayoutAttributes *attributes in layoutAttributesArray) {
if (CGRectIntersectsRect(attributes.frame, rect)) {
[self applyLayoutAttributes:attributes forVisibleRect:visibleRect];
}
}
return layoutAttributesArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *layoutAttributesArray = [super layoutAttributesForItemAtIndexPath:indexPath];
CGRect visibleRect = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, CGRectGetWidth(self.collectionView.bounds), CGRectGetHeight(self.collectionView.bounds));
[self applyLayoutAttributes:layoutAttributesArray forVisibleRect:visibleRect];
return layoutAttributesArray;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
#pragma mark - 设置吸附效果
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
CGRect proposedRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray* array = [self layoutAttributesForElementsInRect:proposedRect];
for (UICollectionViewLayoutAttributes* layoutAttributes in array)
{
if (layoutAttributes.representedElementCategory != UICollectionElementCategoryCell)
continue;
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (fabs(itemHorizontalCenter - horizontalCenter) < fabs(offsetAdjustment))
{
offsetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
#pragma mark -
-(void)prepareForCollectionViewUpdates:(NSArray *)updateItems {
[super prepareForCollectionViewUpdates:updateItems];
self.deleteIndexPaths = [NSMutableArray array];
self.insertIndexPaths = [NSMutableArray array];
for (UICollectionViewUpdateItem *update in updateItems) {
if (update.updateAction == UICollectionUpdateActionDelete) {
[self.deleteIndexPaths addObject:update.indexPathBeforeUpdate];
} else if (update.updateAction == UICollectionUpdateActionInsert) {
[self.insertIndexPaths addObject:update.indexPathAfterUpdate];
}
}
}
-(void)finalizeCollectionViewUpdates {
[super finalizeCollectionViewUpdates];
self.deleteIndexPaths = nil;
self.insertIndexPaths = nil;
}
#pragma mark - collectionView增加删除动画
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath];
if ([self.insertIndexPaths containsObject:itemIndexPath]) {
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
attributes.center = CGPointMake(CGRectGetMidX(self.collectionView.frame), CGRectGetMidY(self.collectionView.frame));
attributes.transform = CGAffineTransformMakeScale(0.5, 0.5);
attributes.alpha = 0.0;
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath];
if ([self.deleteIndexPaths containsObject:itemIndexPath]) {
if (!attributes)
attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
attributes.transform = CGAffineTransformTranslate(CGAffineTransformMakeScale(0.5, 0.5), 0.05, 0.05);
NSUInteger count = [self.collectionView numberOfItemsInSection:0];
BOOL isLastIndex = itemIndexPath.row== count?YES:NO;
if (count * _itemWidth < kScreenWidth || !isLastIndex) {
attributes.center = CGPointMake( attributes.center.x, -CGRectGetHeight(self.collectionView.frame));
NSLog(@"~~~~count:%lu", (unsigned long)count);
} else if(isLastIndex) {
count++;
NSUInteger rightEdge = self.collectionView.contentInset.right;
NSUInteger leftEdge = self.collectionView.contentInset.left;
int x = count * _itemWidth + (count -1)*10 - rightEdge - leftEdge - kScreenWidth/2;
NSLog(@"!!!!count:%lu x:%d", (unsigned long)count, x);
attributes.center = CGPointMake(x, -CGRectGetHeight(self.collectionView.frame));
}
attributes.alpha = 0.0;
}
return attributes;
}
@end