TYCyclePagerTransformLayout.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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. if (_layout.scrollDirection == TYCyclePagerScrollDirectionHorizontal) {
  51. self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  52. } else {
  53. self.scrollDirection = UICollectionViewScrollDirectionVertical;
  54. }
  55. }
  56. - (CGSize)itemSize {
  57. if (!_layout) {
  58. return [super itemSize];
  59. }
  60. return _layout.itemSize;
  61. }
  62. - (CGFloat)minimumLineSpacing {
  63. if (!_layout) {
  64. return [super minimumLineSpacing];
  65. }
  66. return _layout.itemSpacing;
  67. }
  68. - (CGFloat)minimumInteritemSpacing {
  69. if (!_layout) {
  70. return [super minimumInteritemSpacing];
  71. }
  72. return _layout.itemSpacing;
  73. }
  74. - (TYTransformLayoutItemDirection)directionWithCenter:(CGPoint)center {
  75. TYTransformLayoutItemDirection direction= TYTransformLayoutItemRight;
  76. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  77. CGFloat centerY = center.y;
  78. CGFloat contentCenterY = self.collectionView.contentOffset.y + CGRectGetHeight(self.collectionView.frame)/2;
  79. if (ABS(centerY - contentCenterY) < 0.5) {
  80. direction = TYTransformLayoutItemCenter;
  81. }else if (centerY - contentCenterY < 0) {
  82. direction = TYTransformLayoutItemLeft;
  83. }
  84. return direction;
  85. }
  86. CGFloat contentCenterX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.frame)/2;
  87. CGFloat centerX = center.x;
  88. if (ABS(centerX - contentCenterX) < 0.5) {
  89. direction = TYTransformLayoutItemCenter;
  90. }else if (centerX - contentCenterX < 0) {
  91. direction = TYTransformLayoutItemLeft;
  92. }
  93. return direction;
  94. }
  95. #pragma mark - layout
  96. -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
  97. {
  98. return _layout.layoutType == TYCyclePagerTransformLayoutNormal ? [super shouldInvalidateLayoutForBoundsChange:newBounds] : YES;
  99. }
  100. - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
  101. if (_delegateFlags.applyTransformToAttributes || _layout.layoutType != TYCyclePagerTransformLayoutNormal) {
  102. NSArray *attributesArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES];
  103. CGRect visibleRect = {self.collectionView.contentOffset,self.collectionView.bounds.size};
  104. for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
  105. if (!CGRectIntersectsRect(visibleRect, attributes.frame)) {
  106. continue;
  107. }
  108. if (_delegateFlags.applyTransformToAttributes) {
  109. [_delegate pagerViewTransformLayout:self applyTransformToAttributes:attributes];
  110. }else {
  111. [self applyTransformToAttributes:attributes layoutType:_layout.layoutType];
  112. }
  113. }
  114. return attributesArray;
  115. }
  116. return [super layoutAttributesForElementsInRect:rect];
  117. }
  118. - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  119. UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];
  120. if (_delegateFlags.initializeTransformAttributes) {
  121. [_delegate pagerViewTransformLayout:self initializeTransformAttributes:attributes];
  122. }else if(_layout.layoutType != TYCyclePagerTransformLayoutNormal){
  123. [self initializeTransformAttributes:attributes layoutType:_layout.layoutType];
  124. }
  125. return attributes;
  126. }
  127. #pragma mark - transform
  128. - (void)initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(TYCyclePagerTransformLayoutType)layoutType {
  129. switch (layoutType) {
  130. case TYCyclePagerTransformLayoutLinear:
  131. [self applyLinearTransformToAttributes:attributes scale:_layout.minimumScale alpha:_layout.minimumAlpha];
  132. break;
  133. case TYCyclePagerTransformLayoutCoverflow:
  134. {
  135. [self applyCoverflowTransformToAttributes:attributes angle:_layout.maximumAngle alpha:_layout.minimumAlpha];
  136. break;
  137. }
  138. default:
  139. break;
  140. }
  141. }
  142. - (void)applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(TYCyclePagerTransformLayoutType)layoutType {
  143. switch (layoutType) {
  144. case TYCyclePagerTransformLayoutLinear:
  145. [self applyLinearTransformToAttributes:attributes];
  146. break;
  147. case TYCyclePagerTransformLayoutCoverflow:
  148. [self applyCoverflowTransformToAttributes:attributes];
  149. break;
  150. default:
  151. break;
  152. }
  153. }
  154. #pragma mark - LinearTransform
  155. - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes {
  156. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  157. CGFloat collectionHeight = self.collectionView.frame.size.height;
  158. if (collectionHeight <= 0) {
  159. return;
  160. }
  161. CGFloat centetY = self.collectionView.contentOffset.y + collectionHeight/2;
  162. CGFloat delta = ABS(attributes.center.y - centetY);
  163. CGFloat scale = MAX(1 - delta/collectionHeight*_layout.rateOfChange, _layout.minimumScale);
  164. CGFloat alpha = MAX(1 - delta/collectionHeight, _layout.minimumAlpha);
  165. [self applyLinearTransformToAttributes:attributes scale:scale alpha:alpha];
  166. }
  167. CGFloat collectionViewWidth = self.collectionView.frame.size.width;
  168. if (collectionViewWidth <= 0) {
  169. return;
  170. }
  171. CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2;
  172. CGFloat delta = ABS(attributes.center.x - centetX);
  173. CGFloat scale = MAX(1 - delta/collectionViewWidth*_layout.rateOfChange, _layout.minimumScale);
  174. CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha);
  175. [self applyLinearTransformToAttributes:attributes scale:scale alpha:alpha];
  176. }
  177. - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes scale:(CGFloat)scale alpha:(CGFloat)alpha {
  178. CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
  179. if (_layout.adjustSpacingWhenScroling) {
  180. TYTransformLayoutItemDirection direction = [self directionWithCenter:attributes.center];
  181. CGFloat translate = 0;
  182. switch (direction) {
  183. case TYTransformLayoutItemLeft:
  184. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  185. translate = 1.15 * attributes.size.height*(1-scale)/2;
  186. } else {
  187. translate = 1.15 * attributes.size.width*(1-scale)/2;
  188. }
  189. break;
  190. case TYTransformLayoutItemRight:
  191. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  192. translate = -1.15 * attributes.size.height*(1-scale)/2;
  193. } else {
  194. translate = -1.15 * attributes.size.width*(1-scale)/2;
  195. }
  196. break;
  197. default:
  198. // center
  199. scale = 1.0;
  200. alpha = 1.0;
  201. break;
  202. }
  203. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  204. transform = CGAffineTransformTranslate(transform,0, translate);
  205. } else {
  206. transform = CGAffineTransformTranslate(transform,translate, 0);
  207. }
  208. }
  209. attributes.transform = transform;
  210. attributes.alpha = alpha;
  211. }
  212. #pragma mark - CoverflowTransform
  213. - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes{
  214. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  215. CGFloat collectionViewHeight = self.collectionView.frame.size.height;
  216. if (collectionViewHeight <= 0) {
  217. return;
  218. }
  219. CGFloat centetY = self.collectionView.contentOffset.y + collectionViewHeight/2;
  220. CGFloat delta = ABS(attributes.center.y - centetY);
  221. CGFloat angle = MIN(delta/collectionViewHeight*(1-_layout.rateOfChange), _layout.maximumAngle);
  222. CGFloat alpha = MAX(1 - delta/collectionViewHeight, _layout.minimumAlpha);
  223. [self applyCoverflowTransformToAttributes:attributes angle:angle alpha:alpha];
  224. }
  225. CGFloat collectionViewWidth = self.collectionView.frame.size.width;
  226. if (collectionViewWidth <= 0) {
  227. return;
  228. }
  229. CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2;
  230. CGFloat delta = ABS(attributes.center.x - centetX);
  231. CGFloat angle = MIN(delta/collectionViewWidth*(1-_layout.rateOfChange), _layout.maximumAngle);
  232. CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha);
  233. [self applyCoverflowTransformToAttributes:attributes angle:angle alpha:alpha];
  234. }
  235. - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes angle:(CGFloat)angle alpha:(CGFloat)alpha {
  236. TYTransformLayoutItemDirection direction = [self directionWithCenter:attributes.center];
  237. CATransform3D transform3D = CATransform3DIdentity;
  238. transform3D.m34 = -0.002;
  239. CGFloat translate = 0;
  240. switch (direction) {
  241. case TYTransformLayoutItemLeft:
  242. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  243. translate = (1-cos(angle*1.2*M_PI))*attributes.size.height;
  244. } else {
  245. translate = (1-cos(angle*1.2*M_PI))*attributes.size.width;
  246. }
  247. break;
  248. case TYTransformLayoutItemRight:
  249. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  250. translate = -(1-cos(angle*1.2*M_PI))*attributes.size.height;
  251. } else {
  252. translate = -(1-cos(angle*1.2*M_PI))*attributes.size.width;
  253. }
  254. angle = -angle;
  255. break;
  256. default:
  257. // center
  258. angle = 0;
  259. alpha = 1;
  260. break;
  261. }
  262. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  263. transform3D = CATransform3DRotate(transform3D, M_PI*angle, 1, 0, 0);
  264. } else {
  265. transform3D = CATransform3DRotate(transform3D, M_PI*angle, 0, 1, 0);
  266. }
  267. if (_layout.adjustSpacingWhenScroling) {
  268. if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
  269. transform3D = CATransform3DTranslate(transform3D, 0, translate, 0);
  270. } else {
  271. transform3D = CATransform3DTranslate(transform3D, translate, 0, 0);
  272. }
  273. }
  274. attributes.transform3D = transform3D;
  275. attributes.alpha = alpha;
  276. }
  277. @end
  278. @implementation TYCyclePagerViewLayout
  279. - (instancetype)init {
  280. if (self = [super init]) {
  281. _itemVerticalCenter = YES;
  282. _minimumScale = 0.8;
  283. _minimumAlpha = 1.0;
  284. _maximumAngle = 0.2;
  285. _rateOfChange = 0.4;
  286. _adjustSpacingWhenScroling = YES;
  287. _scrollDirection = TYCyclePagerScrollDirectionHorizontal;
  288. }
  289. return self;
  290. }
  291. #pragma mark - getter
  292. - (UIEdgeInsets)onlyOneSectionInset {
  293. if (_scrollDirection == TYCyclePagerScrollDirectionVertical) {
  294. CGFloat bottomSpace = _pageView && !_isInfiniteLoop && _itemVerticalCenter ? (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2 : _sectionInset.bottom;
  295. CGFloat topSpace = _pageView && !_isInfiniteLoop && _itemVerticalCenter ? (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2 : _sectionInset.top;
  296. if (_itemHorizontalCenter) {
  297. CGFloat horizontalSpace = (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2;
  298. return UIEdgeInsetsMake(topSpace, horizontalSpace, bottomSpace, horizontalSpace);
  299. }
  300. return UIEdgeInsetsMake(topSpace, _sectionInset.left, bottomSpace, _sectionInset.right);
  301. }
  302. CGFloat leftSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.left;
  303. CGFloat rightSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.right;
  304. if (_itemVerticalCenter) {
  305. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  306. return UIEdgeInsetsMake(verticalSpace, leftSpace, verticalSpace, rightSpace);
  307. }
  308. return UIEdgeInsetsMake(_sectionInset.top, leftSpace, _sectionInset.bottom, rightSpace);
  309. }
  310. - (UIEdgeInsets)firstSectionInset {
  311. if (_scrollDirection == TYCyclePagerScrollDirectionVertical) {
  312. if (_itemHorizontalCenter) {
  313. CGFloat horizontalSpace = (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2;
  314. return UIEdgeInsetsMake(_sectionInset.top, horizontalSpace, _itemSpacing, horizontalSpace);
  315. }
  316. return UIEdgeInsetsMake(_sectionInset.top, _sectionInset.left, _itemSpacing, _sectionInset.right);
  317. } else {
  318. if (_itemVerticalCenter) {
  319. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  320. return UIEdgeInsetsMake(verticalSpace, _sectionInset.left, verticalSpace, _itemSpacing);
  321. }
  322. return UIEdgeInsetsMake(_sectionInset.top, _sectionInset.left, _sectionInset.bottom, _itemSpacing);
  323. }
  324. }
  325. - (UIEdgeInsets)lastSectionInset {
  326. if (_scrollDirection == TYCyclePagerScrollDirectionVertical) {
  327. if (_itemHorizontalCenter) {
  328. CGFloat horizontalSpace = (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2;
  329. return UIEdgeInsetsMake(0, horizontalSpace, _sectionInset.bottom, horizontalSpace);
  330. }
  331. return UIEdgeInsetsMake(0, _sectionInset.left, _sectionInset.bottom, _sectionInset.right);
  332. } else {
  333. if (_itemVerticalCenter) {
  334. CGFloat horizontalSpace = (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2;
  335. return UIEdgeInsetsMake(0, horizontalSpace, _sectionInset.bottom, horizontalSpace);
  336. }
  337. return UIEdgeInsetsMake(_sectionInset.top, 0, _sectionInset.bottom, _sectionInset.right);
  338. }
  339. }
  340. - (UIEdgeInsets)middleSectionInset {
  341. if (_scrollDirection == TYCyclePagerScrollDirectionVertical) {
  342. if (_itemHorizontalCenter) {
  343. CGFloat horizontalSpace = (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2;
  344. return UIEdgeInsetsMake(0, horizontalSpace, _itemSpacing, horizontalSpace);
  345. }
  346. } else {
  347. if (_itemVerticalCenter) {
  348. CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2;
  349. return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _itemSpacing);
  350. }
  351. }
  352. return _sectionInset;
  353. }
  354. @end