準備數據 首先先加入一些資源文件:先建立一個xcassets文件,放入圖片:再建立一個plist文件,寫入與圖片對應的內容:在ViewController中讀取plist到詞典中:
先簡單看一下效果:
原文地址:http://www.wossoneri.com/2016/01/09/[iOS]%20UICollectionView%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E6%B0%B4%E5%B9%B3%E6%BB%9A%E5%8A%A8/
準備數據
首先先加入一些資源文件:
先建立一個xcassets
文件,放入圖片:
再建立一個plist文件,寫入與圖片對應的內容:
在ViewController中讀取plist
到詞典中:
@property (nonatomic, strong) NSArray *itemTitles;
NSString *path = [[NSBundle mainBundle] pathForResource:@"titles" ofType:@"plist"];
NSDictionary *rootDictionary = [[NSDictionary alloc] initWithContentsOfFile:path];
self.itemTitles = [rootDictionary objectForKey:@"heros"];
可以打log
輸出,可以看到plist
的內容已經讀取出來,後面就可以用_itemTitle
作為數據源了。
添加UICollectionView初步顯示圖片
每個CollectionView
都有一個對應的佈局layout
,對於預設的的UICollectionViewFlowLayout
,效果是類似Android的GridView
的佈局。如果要自定義CollectionView
的樣式,就要對這個layout
進行修改。
建立自己的HorizontalFlowLayout
,繼承自UICollectionViewFlowLayout
,然後在初始化方法里將滾動方向設置為水平:
- (instancetype) init {
if (self = [super init]) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
}
return self;
}
接下來定製我們的cell
的顯示樣式,建立DotaCell
,繼承自UICollectionViewCell
。由於我們要實現的是圖片和文字的上下佈局,所以增加兩個屬性:
@interface DotaCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *image;
@property (nonatomic, strong) UILabel *name;
@end
然後設置圖片與文字上下對齊佈局,這裡我使用pod
導入Masonry
庫來寫自動佈局:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initialize];
}
return self;
}
- (void)initialize {
self.layer.doubleSided = NO;
self.image = [[UIImageView alloc] init];
self.image.backgroundColor = [UIColor clearColor];
self.image.contentMode = UIViewContentModeCenter;
self.image.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.name = [[UILabel alloc] init];
self.name.font = [UIFont fontWithName:@"Helvetica Neue" size:20];
self.name.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:self.image];
[self.contentView addSubview:self.name];
[_image mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.top.equalTo(self.contentView).offset(30);
make.bottom.equalTo(_name.mas_top).offset(-10);
}];
[_name mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.contentView);
make.top.equalTo(_image.mas_bottom).offset(10);
make.bottom.equalTo(self.contentView).offset(-20);
}];
}
寫好layout
和cell
後就可以用這兩個類來初始化我們的collectionView
了:
//add in view did load
self.layout = [[HorizontalFlowLayout alloc] init];
CGRect rct = self.view.bounds;
rct.size.height = 150;
rct.origin.y = [[UIScreen mainScreen] bounds].size.height / 2.0 - rct.size.height;
self.collectionView = [[UICollectionView alloc] initWithFrame:rct collectionViewLayout:_layout];
self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.collectionView.showsHorizontalScrollIndicator = NO;
self.collectionView.decelerationRate = UIScrollViewDecelerationRateNormal;
[self.collectionView registerClass:[DotaCell class] forCellWithReuseIdentifier:NSStringFromClass([DotaCell class])];
[self.collectionView setBackgroundColor:[UIColor clearColor]];
[self.collectionView setDelegate:self];
[self.collectionView setDataSource:self];
[self.view addSubview:_collectionView];
添加UICollectionViewDataSource
的代理方法,使其顯示數據。
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.itemTitles count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
DotaCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([DotaCell class]) forIndexPath:indexPath];
cell.image.image = [UIImage imageNamed:[self.itemTitles objectAtIndex:indexPath.row]];
cell.name.text = [self.itemTitles objectAtIndex:indexPath.row];
return cell;
}
這樣程式就有了我們想要的初步效果:
圖片水平排放
但...效果的確很差!
下麵要做的就是逐步完善效果,首先我們要讓兩排圖像變成一排去展示。那要怎麼去做?首先,我們在初始化collectionView
的地方設置了高度為150,所以圖片就擠在這個150的高度里儘可能的壓縮顯示。由於collectionView
的尺寸已經設定,那麼就剩cell
的尺寸可以控制了。實現CollectionViewFlowLayoutDelegate
的代理方法sizeForItemAtIndexPath
:
- (CGSize)collectionView:(nonnull UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
return CGSizeMake(64, collectionView.bounds.size.height);
}
這裡寬度64是圖片的尺寸,高度設置填滿
collectionView
的高度是為了防止上圖中兩行圖片擠壓的情況,所以直接讓一個cell
的高度占滿整個容器。
這時候的效果好了很多,已經有點樣子了:
頂端圖片滑到中間
但這離我們最終的效果還差很遠,接下來我需要實現讓第一張圖片和最後一張圖片都能滑到屏幕中點的位置,這應該是很常見的效果,實現起來也很簡單。首先我們的一排cell
都預設為頂端與collectionView
的兩端對齊的,collectionView
的左右兩端與viewController.view
也是對齊的,所以顯示的效果是,兩端的圖片都與屏幕對齊。知道這個關係就好辦了,直接設置collectionView
與其父view
的內間距即可。
依舊是實現flowLayout
的代理方法:
//Asks the delegate for the margins to apply to content in the specified section.安排初始位置
//使前後項都能居中顯示
- (UIEdgeInsets)collectionView:(nonnull UICollectionView *)collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
NSInteger itemCount = [self collectionView:collectionView numberOfItemsInSection:section];
NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
CGSize firstSize = [self collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:firstIndexPath];
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:itemCount - 1 inSection:section];
CGSize lastSize = [self collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:lastIndexPath];
return UIEdgeInsetsMake(0, (collectionView.bounds.size.width - firstSize.width) / 2,
0, (collectionView.bounds.size.width - lastSize.width) / 2);
}
效果如圖:
居中圖片放大顯示
接下來添加一個我們需要的特效,就是中間的圖片放大顯示,其餘的縮小並且增加一層半透明效果。
在FlowLayout
中有一個名為layoutAttributesForElementsInRect
的方法,功能如其名,就是設置範圍內元素的layout
屬性。對於這個效果,首先需要設置放大的比例,其次要根據圖片大小和間距來設定一個合適的觸發放大的區域寬度,當圖滑入這個區域就進行縮放。
static CGFloat const ActiveDistance = 80;
static CGFloat const ScaleFactor = 0.2;
//這裡設置放大範圍
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect = (CGRect){self.collectionView.contentOffset, self.collectionView.bounds.size};
for (UICollectionViewLayoutAttributes *attributes in array) {
//如果cell在屏幕上則進行縮放
if (CGRectIntersectsRect(attributes.frame, rect)) {
attributes.alpha = 0.5;
CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;//距離中點的距離
CGFloat normalizedDistance = distance / ActiveDistance;
if (ABS(distance) < ActiveDistance) {
CGFloat zoom = 1 + ScaleFactor * (1 - ABS(normalizedDistance)); //放大漸變
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
attributes.zIndex = 1;
attributes.alpha = 1.0;
}
}
}
return array;
}
效果如下:
滑動校正
這時候幾乎完成了,但還差點東西,就是讓其在滾動停止的時候,離屏幕中間最近的cell
自動矯正位置到中間。還是在FlowLayout
添加該方法,具體說明我都寫到註釋里了:
//scroll 停止對中間位置進行偏移量校正
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGFloat offsetAdjustment = MAXFLOAT;
//// |-------[-------]-------|
//// |滑動偏移|可視區域 |剩餘區域|
//是整個collectionView在滑動偏移後的當前可見區域的中點
CGFloat centerX = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
// CGFloat centerX = self.collectionView.center.x; //這個中點始終是屏幕中點
//所以這裡對collectionView的具體尺寸不太理解,輸出的是屏幕大小,但實際上寬度肯定超出屏幕的
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *layoutAttr in array) {
CGFloat itemCenterX = layoutAttr.center.x;
if (ABS(itemCenterX - centerX) < ABS(offsetAdjustment)) { // 找出最小的offset 也就是最中間的item 偏移量
offsetAdjustment = itemCenterX - centerX;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
增加圖片點擊效果
最後 添加一個點擊cell 將其滾動到中間
在viewcontroller
添加CollectionViewDelegate
的代理方法
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
//滾動到中間
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
封裝成控制項
當我們把效果實現之後,就可以考慮將代碼優化一下,合到一個類里,減少書寫常量,增加介面,封裝成一個控制項去使用。比如可以設定文字的顯示與隱藏介面,再比如增加適應各種尺寸的圖片等等。這個代碼就不放了,畢竟不難,有問題給我留言好了。