天天看點

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

繼續閱讀