uicollectionview真的好强大,今天我们来研究一下这种很常见的卡片动画效果是如何实现了。本篇不能太深入地讲解,因为笔者也是刚刚摸索出点眉目,但是并没有深刻地理解。如果在讲解过程中,出现不对的地方,请及时反馈。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLv1WZkRmchN2LcNDMvwlNxAjMvw1ckF2bsBXdvwFduVGdu92YtA3dvwVbvNmLvVHazlmblhmL3d3dvw1LcpDc0RHaiojIsJye.gif)
1
2
3
4
5
6
7
8
9
10
11
12
// 我们必须重写此方法,指定布局大小
// 每次layout invalidated或者重新query布局信息时,会调用
- (void)preparelayout;
// 用于决定布局信息
// 我们必须重写它来实现布局信息
- (nullable nsarray<__kindof uicollectionviewlayoutattributes *> *)layoutattributesforelementsinrect:(cgrect)rect;
// 重写它来布局信息
- (nullable uicollectionviewlayoutattributes *)layoutattributesforitematindexpath:(nsindexpath *)indexpath;
还有一个非常关键的api,必须重写:
// return yes to cause the collection view to requery the layout for geometry information
// 当重新查询布局信息时,就会调用此api。要设置为yes,才能实现自定义布局。
- (bool)shouldinvalidatelayoutforboundschange:(cgrect)newbounds;
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//
// hybcardflowlayout.m
// collectionviewdemos
// created by huangyibiao on 16/3/26.
// copyright © 2016年 huangyibiao. all rights reserved.
#import "hybcardflowlayout.h"
@interface hybcardflowlayout ()
@property (nonatomic, strong) nsindexpath *mainindexpath;
@property (nonatomic, strong) nsindexpath *willmovetomainindexpath;
@end
@implementation hybcardflowlayout
- (void)preparelayout {
cgfloat inset = 32;
self.itemsize = cgsizemake(self.collectionview.frame.size.width - 2 * inset,
self.collectionview.frame.size.height * 3 / 4);
self.sectioninset = uiedgeinsetsmake(0, inset, 0, inset);
self.scrolldirection = uicollectionviewscrolldirectionhorizontal;
[super preparelayout];
}
#pragma mark - override
- (bool)shouldinvalidatelayoutforboundschange:(cgrect)newbounds {
return yes;
- (uicollectionviewlayoutattributes *)layoutattributesforitematindexpath:(nsindexpath *)indexpath {
uicollectionviewlayoutattributes *attribute = [super layoutattributesforitematindexpath:indexpath];
[self settransformforlayoutattributes:attribute];
return attribute;
- (nsarray<uicollectionviewlayoutattributes *> *)layoutattributesforelementsinrect:(cgrect)rect {
nsarray *attributessuper = [super layoutattributesforelementsinrect:rect];
// 一定要深复制一份,不能修改父类的属性,不然会有很多error打印出来
nsarray *attributes = [[nsarray alloc] initwitharray:attributessuper copyitems:yes];
nsarray *visibleindexpaths = [self.collectionview indexpathsforvisibleitems];
if (visibleindexpaths.count <= 0) {
return attributes;
} else if (visibleindexpaths.count == 1) {
self.mainindexpath = [visibleindexpaths firstobject];
self.willmovetomainindexpath = nil;
} else if (visibleindexpaths.count == 2) {
nsindexpath *indexpath = [visibleindexpaths firstobject];
// 说明是往左滚动
if (indexpath == self.mainindexpath) {
// 记录将要移进来的位置
self.willmovetomainindexpath = visibleindexpaths[1];
} else {// 往右滚动
self.willmovetomainindexpath = visibleindexpaths[0];
// 更新下一个成为main
self.mainindexpath = visibleindexpaths[1];
}
}
for (uicollectionviewlayoutattributes *attribute in attributes) {
[self settransformforlayoutattributes:attribute];
return attributes;
- (void)settransformforlayoutattributes:(uicollectionviewlayoutattributes *)attribute {
uicollectionviewcell *cell = [self.collectionview cellforitematindexpath:attribute.indexpath];
if (self.mainindexpath && attribute.indexpath.section == self.mainindexpath.section) {
attribute.transform3d = [self tranformforview:cell];
} else if (self.willmovetomainindexpath && attribute.indexpath.section == self.willmovetomainindexpath.section) {
- (catransform3d)tranformforview:(uicollectionviewcell *)view {
// cell的起始偏移
cgfloat w = self.collectionview.frame.size.width;
cgfloat offset = [self.collectionview indexpathforcell:view].section * w;
// 当前偏移
cgfloat currentoffset = self.collectionview.contentoffset.x;
// 计算偏移angle
cgfloat angle = (currentoffset - offset) / w;
catransform3d t = catransform3didentity;
t.m34 = 1.0 / -500;
if (currentoffset - offset >= 0) {
t = catransform3drotate(t, angle, 1, 1, 0);
} else {
t = catransform3drotate(t, angle, -1, 1, 0);
return t;
这里主要是要处理旋转。然后要处理切换cell的attribute设置。mainindexpath属性用于记录当前显示的cell的位置。willmovetomainindexpath记录将要出现的cell的位置。
这里在慢慢切换时,效果是挺好的,但是如果快速切换卡片,你会发现会有一点点不好之处,就是下一个cell突然出现的。