TYCyclePagerTransformLayout.m 15 KB

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