TYCyclePagerTransformLayout.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. //
  2. // TYCyclePagerViewLayout.m
  3. // TYCyclePagerViewDemo
  4. //
  5. // Created by tany on 2017/6/19.
  6. // Copyright © 2017年 tany. All rights reserved.
  7. //
  8. #import "TYCyclePagerTransformLayout.h"
  9. typedef NS_ENUM(NSUInteger, TYTransformLayoutItemDirection) {
  10. TYTransformLayoutItemLeft,
  11. TYTransformLayoutItemCenter,
  12. TYTransformLayoutItemRight,
  13. };
  14. @interface TYCyclePagerTransformLayout () {
  15. struct {
  16. unsigned int applyTransformToAttributes :1;
  17. unsigned int initializeTransformAttributes :1;
  18. }_delegateFlags;
  19. }
  20. @property (nonatomic, assign) BOOL applyTransformToAttributesDelegate;
  21. @end
  22. @interface TYCyclePagerViewLayout ()
  23. @property (nonatomic, weak) UIView *pageView;
  24. @end
  25. @implementation TYCyclePagerTransformLayout
  26. - (instancetype)init {
  27. if (self = [super init]) {
  28. self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  29. }
  30. return self;
  31. }
  32. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  33. if (self = [super initWithCoder:aDecoder]) {
  34. self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  35. }
  36. return self;
  37. }
  38. #pragma mark - getter setter
  39. - (void)setDelegate:(id<TYCyclePagerTransformLayoutDelegate>)delegate {
  40. _delegate = delegate;
  41. _delegateFlags.initializeTransformAttributes = [delegate respondsToSelector:@selector(pagerViewTransformLayout:initializeTransformAttributes:)];
  42. _delegateFlags.applyTransformToAttributes = [delegate respondsToSelector:@selector(pagerViewTransformLayout:applyTransformToAttributes:)];
  43. }
  44. - (void)setLayout:(TYCyclePagerViewLayout *)layout {
  45. _layout = layout;
  46. _layout.pageView = self.collectionView;
  47. self.itemSize = _layout.itemSize;
  48. self.minimumInteritemSpacing = _layout.itemSpacing;
  49. self.minimumLineSpacing = _layout.itemSpacing;
  50. }
  51. - (CGSize)itemSize {
  52. if (!_layout) {
  53. return [super itemSize];
  54. }
  55. return _layout.itemSize;
  56. }
  57. - (CGFloat)minimumLineSpacing {
  58. if (!_layout) {
  59. return [super minimumLineSpacing];
  60. }
  61. return _layout.itemSpacing;
  62. }
  63. - (CGFloat)minimumInteritemSpacing {
  64. if (!_layout) {
  65. return [super minimumInteritemSpacing];
  66. }
  67. return _layout.itemSpacing;
  68. }
  69. - (TYTransformLayoutItemDirection)directionWithCenterX:(CGFloat)centerX {
  70. TYTransformLayoutItemDirection direction= TYTransformLayoutItemRight;
  71. CGFloat contentCenterX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.frame)/2;
  72. if (ABS(centerX - contentCenterX) < 0.5) {
  73. direction = TYTransformLayoutItemCenter;
  74. }else if (centerX - contentCenterX < 0) {
  75. direction = TYTransformLayoutItemLeft;
  76. }
  77. return direction;
  78. }
  79. #pragma mark - layout
  80. -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
  81. {
  82. return _layout.layoutType == TYCyclePagerTransformLayoutNormal ? [super shouldInvalidateLayoutForBoundsChange:newBounds] : YES;
  83. }
  84. - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
  85. if (_delegateFlags.applyTransformToAttributes || _layout.layoutType != TYCyclePagerTransformLayoutNormal) {
  86. NSArray *attributesArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
  87. CGRect visibleRect = {self.collectionView.contentOffset,self.collectionView.bounds.size};
  88. for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
  89. if (!CGRectIntersectsRect(visibleRect, attributes.frame)) {
  90. continue;
  91. }
  92. if (_delegateFlags.applyTransformToAttributes) {
  93. [_delegate pagerViewTransformLayout:self applyTransformToAttributes:attributes];
  94. }else {
  95. [self applyTransformToAttributes:attributes layoutType:_layout.layoutType];
  96. }
  97. }
  98. return attributesArray;
  99. }
  100. return [super layoutAttributesForElementsInRect:rect];
  101. }
  102. - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  103. UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
  104. if (_delegateFlags.initializeTransformAttributes) {
  105. [_delegate pagerViewTransformLayout:self initializeTransformAttributes:attributes];
  106. }else if(_layout.layoutType != TYCyclePagerTransformLayoutNormal){
  107. [self initializeTransformAttributes:attributes layoutType:_layout.layoutType];
  108. }
  109. return attributes;
  110. }
  111. #pragma mark - transform
  112. - (void)initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(TYCyclePagerTransformLayoutType)layoutType {
  113. switch (layoutType) {
  114. case TYCyclePagerTransformLayoutLinear:
  115. [self applyLinearTransformToAttributes:attributes scale:_layout.minimumScale alpha:_layout.minimumAlpha];
  116. break;
  117. case TYCyclePagerTransformLayoutCoverflow:
  118. {
  119. [self applyCoverflowTransformToAttributes:attributes angle:_layout.maximumAngle alpha:_layout.minimumAlpha];
  120. break;
  121. }
  122. default:
  123. break;
  124. }
  125. }
  126. - (void)applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(TYCyclePagerTransformLayoutType)layoutType {
  127. switch (layoutType) {
  128. case TYCyclePagerTransformLayoutLinear:
  129. [self applyLinearTransformToAttributes:attributes];
  130. break;
  131. case TYCyclePagerTransformLayoutCoverflow:
  132. [self applyCoverflowTransformToAttributes:attributes];
  133. break;
  134. default:
  135. break;
  136. }
  137. }
  138. #pragma mark - LinearTransform
  139. - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes {
  140. CGFloat collectionViewWidth = self.collectionView.frame.size.width;
  141. if (collectionViewWidth <= 0) {
  142. return;
  143. }
  144. CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2;
  145. CGFloat delta = ABS(attributes.center.x - centetX);
  146. CGFloat scale = MAX(1 - delta/collectionViewWidth*_layout.rateOfChange, _layout.minimumScale);
  147. CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha);
  148. [self applyLinearTransformToAttributes:attributes scale:scale alpha:alpha];
  149. }
  150. - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes scale:(CGFloat)scale alpha:(CGFloat)alpha {
  151. CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
  152. if (_layout.adjustSpacingWhenScroling) {
  153. TYTransformLayoutItemDirection direction = [self directionWithCenterX:attributes.center.x];
  154. CGFloat translate = 0;
  155. switch (direction) {
  156. case TYTransformLayoutItemLeft:
  157. translate = 1.15 * attributes.size.width*(1-scale)/2;
  158. break;
  159. case TYTransformLayoutItemRight:
  160. translate = -1.15 * attributes.size.width*(1-scale)/2;
  161. break;
  162. default:
  163. // center
  164. scale = 1.0;
  165. alpha = 1.0;
  166. break;
  167. }
  168. transform = CGAffineTransformTranslate(transform,translate, 0);
  169. }
  170. attributes.transform = transform;
  171. attributes.alpha = alpha;
  172. }
  173. #pragma mark - CoverflowTransform
  174. - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes{
  175. CGFloat collectionViewWidth = self.collectionView.frame.size.width;
  176. if (collectionViewWidth <= 0) {
  177. return;
  178. }
  179. CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2;
  180. CGFloat delta = ABS(attributes.center.x - centetX);
  181. CGFloat angle = MIN(delta/collectionViewWidth*(1-_layout.rateOfChange), _layout.maximumAngle);
  182. CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha);
  183. [self applyCoverflowTransformToAttributes:attributes angle:angle alpha:alpha];
  184. }
  185. - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes angle:(CGFloat)angle alpha:(CGFloat)alpha {
  186. TYTransformLayoutItemDirection direction = [self directionWithCenterX:attributes.center.x];
  187. CATransform3D transform3D = CATransform3DIdentity;
  188. transform3D.m34 = -0.002;
  189. CGFloat translate = 0;
  190. switch (direction) {
  191. case TYTransformLayoutItemLeft:
  192. translate = (1-cos(angle*1.2*M_PI))*attributes.size.width;
  193. break;
  194. case TYTransformLayoutItemRight:
  195. translate = -(1-cos(angle*1.2*M_PI))*attributes.size.width;
  196. angle = -angle;
  197. break;
  198. default:
  199. // center
  200. angle = 0;
  201. alpha = 1;
  202. break;
  203. }
  204. transform3D = CATransform3DRotate(transform3D, M_PI*angle, 0, 1, 0);
  205. if (_layout.adjustSpacingWhenScroling) {
  206. transform3D = CATransform3DTranslate(transform3D, translate, 0, 0);
  207. }
  208. attributes.transform3D = transform3D;
  209. attributes.alpha = alpha;
  210. }
  211. @end
  212. @implementation TYCyclePagerViewLayout
  213. - (instancetype)init {
  214. if (self = [super init]) {
  215. _itemVerticalCenter = YES;
  216. _minimumScale = 0.8;
  217. _minimumAlpha = 1.0;
  218. _maximumAngle = 0.2;
  219. _rateOfChange = 0.4;
  220. _adjustSpacingWhenScroling = YES;
  221. }
  222. return self;
  223. }
  224. #pragma mark - getter
  225. - (UIEdgeInsets)onlyOneSectionInset {
  226. CGFloat leftSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.left;
  227. CGFloat rightSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.right;
  228. if (_itemVerticalCenter) {
  229. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  230. return UIEdgeInsetsMake(verticalSpace, leftSpace, verticalSpace, rightSpace);
  231. }
  232. return UIEdgeInsetsMake(_sectionInset.top, leftSpace, _sectionInset.bottom, rightSpace);
  233. }
  234. - (UIEdgeInsets)firstSectionInset {
  235. if (_itemVerticalCenter) {
  236. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  237. return UIEdgeInsetsMake(verticalSpace, _sectionInset.left, verticalSpace, _itemSpacing);
  238. }
  239. return UIEdgeInsetsMake(_sectionInset.top, _sectionInset.left, _sectionInset.bottom, _itemSpacing);
  240. }
  241. - (UIEdgeInsets)lastSectionInset {
  242. if (_itemVerticalCenter) {
  243. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  244. return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _sectionInset.right);
  245. }
  246. return UIEdgeInsetsMake(_sectionInset.top, 0, _sectionInset.bottom, _sectionInset.right);
  247. }
  248. - (UIEdgeInsets)middleSectionInset {
  249. if (_itemVerticalCenter) {
  250. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  251. return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _itemSpacing);
  252. }
  253. return _sectionInset;
  254. }
  255. @end