天天看点

CollectionView旋转水平卡片布局概述效果图重写API自定义布局结尾

uicollectionview真的好强大,今天我们来研究一下这种很常见的卡片动画效果是如何实现了。本篇不能太深入地讲解,因为笔者也是刚刚摸索出点眉目,但是并没有深刻地理解。如果在讲解过程中,出现不对的地方,请及时反馈。

CollectionView旋转水平卡片布局概述效果图重写API自定义布局结尾

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突然出现的。

继续阅读