有弹性,可伸展的UICollectionView 头部视图
滚动视图的反弹效果可能是iOS中最具特色的效果之一。虽然最初只是华而不实,但随着时间的推移,实际上它已经发挥了一些功能用途,像下拉刷新。另一个很好地应用滚动视图的反弹效果的,就是我最近看到的弹性头部视图。
这个效果非常好,当你向下拉动滚动视图时,可以在顶部和底部查看更多的图片内容。你可能已经在Twitter的iOS应用和Airbnb清单中看到类似的效果了。这种效果很容易实现,今天我将向您展示如何去做。
让我们开始吧。首先,创建一个[UICollectionViewFlowLayout][3]的子类。
#import <UIKit/UIKit.h>
@interface StretchyHeaderCollectionViewLayout : UICollectionViewFlowLayout
@end
如果你曾经摆弄自定义过UICollectionView的布局,那么你可能已经意识到它们非常强大的。这是我们的实现:
#import "StretchyHeaderCollectionViewLayout.h"
@implementation StretchyHeaderCollectionViewLayout
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
// This will schedule calls to layoutAttributesForElementsInRect: as the
// collectionView is scrolling.
return YES;
}
- (UICollectionViewScrollDirection)scrollDirection {
// This subclass only supports vertical scrolling.
return UICollectionViewScrollDirectionVertical;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
UICollectionView *collectionView = [self collectionView];
UIEdgeInsets insets = [collectionView contentInset];
CGPoint offset = [collectionView contentOffset];
CGFloat minY = -insets.top;
// First get the superclass attributes.
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
// Check if we've pulled below past the lowest position
if (offset.y < minY) {
// Figure out how much we've pulled down
CGFloat deltaY = fabsf(offset.y - minY);
for (UICollectionViewLayoutAttributes *attrs in attributes) {
// Locate the header attributes
NSString *kind = [attrs representedElementKind];
if (kind == UICollectionElementKindSectionHeader) {
// Adjust the header's height and y based on how much the user
// has pulled down.
CGSize headerSize = [self headerReferenceSize];
CGRect headerRect = [attrs frame];
headerRect.size.height = MAX(minY, headerSize.height + deltaY);
headerRect.origin.y = headerRect.origin.y - deltaY;
[attrs setFrame:headerRect];
break;
}
}
}
return attributes;
}
@end
这个自定义的布局会检查我们是否下拉到最低的偏移量,这意味着我们将开始拉伸头部视图。如果真的是,我们找到这个头部元素,根据拉伸的值来增加它的高度和y轴上的偏移量。
接下来,在你配置你的集合视图的地方,添加下面的代码:
// Create a new instance of our stretchy layout and set the
// default size for our header (for when it's not stretched)
StretchyHeaderCollectionViewLayout *stretchyLayout;
stretchyLayout = [[StretchyHeaderCollectionViewLayout alloc] init];
[stretchyLayout setHeaderReferenceSize:CGSizeMake(320.0, 160.0)];
// Set our custom layout
[collectionView setCollectionViewLayout:stretchyLayout];
// and tell our collection view to always bounce.
[collectionView setAlwaysBounceVertical:YES];
// Then register a class to use for the header.
[collectionView registerClass:[UICollectionReusableView class]
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:@"ident"];
最后一件需要做的事情就是创建我们的头部视图,而且应该是UICollectionReusableView的子类。我们可以添加一个UIImageView作为它的子视图,然后使用autoresizing来让它大小合适:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath {
// You can make header an ivar so we only ever create one
if (!header) {
header = [collectionView dequeueReusableSupplementaryViewOfKind:kind
withReuseIdentifier:@"ident"
forIndexPath:indexPath];
CGRect bounds;
bounds = [header bounds];
UIImageView *imageView;
imageView = [[UIImageView alloc] initWithFrame:bounds];
[imageView setImage:[UIImage imageNamed:@"header-background"]];
// Make sure the contentMode is set to scale proportionally
[imageView setContentMode:UIViewContentModeScaleAspectFill];
// Clip the parts of the image that are not in frame
[imageView setClipsToBounds:YES];
// Set the autoresizingMask to always be the same height as the header
[imageView setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
// Add the image to our header
[header addSubview:imageView];
}
return header;
}
这个autoresizingMask 会保持图片的高度与头部视图的一致。这个contentMode和clipsToBounds属性将居中图片以及裁切多余的内容。
你可以在Github上找到完整的代码。