MSNumberScrollAnimatedView.m 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. //
  2. // MSNumberScrollAnimatedView.m
  3. // MSNumberScrollAnimatedViewDemo
  4. //
  5. // Created by MrSong on 16/5/20.
  6. // Copyright © 2016年 MrSong. All rights reserved.
  7. //
  8. #import "MSNumberScrollAnimatedView.h"
  9. @interface MSNumberScrollAnimatedView ()
  10. @property (nonatomic, strong) NSMutableArray *numbersText; //保存拆分出来的数字
  11. @property (nonatomic, strong) NSMutableArray *scrollLayers;
  12. @property (nonatomic, strong) NSMutableArray *scrollViews; // 保存imageView
  13. @property (nonatomic, strong) NSNumber *previousNumber; // 保存上一次的数字
  14. @property (nonatomic, copy) NSArray *numberImages;
  15. /** vipType 类型 */
  16. @property (nonatomic, assign) NSInteger vipType;
  17. @end
  18. @implementation MSNumberScrollAnimatedView
  19. #pragma mark - Life Cycle
  20. - (instancetype)initWithVipType:(NSInteger)vipType {
  21. if (self = [super init]) {
  22. _vipType = vipType;
  23. [self commonInit];
  24. }
  25. return self;
  26. }
  27. - (void)layoutSubviews {
  28. [super layoutSubviews];
  29. // 如果已经设置了number但还没有准备动画,在布局完成后准备
  30. if (_number && _scrollLayers.count == 0) {
  31. [self prepareAnimations];
  32. }
  33. }
  34. #pragma mark - Public Methods
  35. - (void)reloadView {
  36. [self prepareAnimations];
  37. }
  38. - (void)startAnimation {
  39. [self createAnimations];
  40. }
  41. - (void)stopAnimation {
  42. for (CALayer *layer in _scrollLayers) {
  43. [layer removeAnimationForKey:@"MSNumberScrollAnimatedView"];
  44. }
  45. }
  46. #pragma mark - Private Methods
  47. - (void)commonInit {
  48. self.duration = 0.1;
  49. self.durationOffset = 0.1;
  50. self.density = 1;
  51. self.minLength = 0;
  52. self.isAscending = NO;
  53. _numbersText = [NSMutableArray array];
  54. _scrollLayers = [NSMutableArray array];
  55. _scrollViews = [NSMutableArray array];
  56. _previousNumber = nil;
  57. if (self.vipType > 0) {
  58. self.numberImages = @[
  59. [UIImage imageNamed:@"gift_vip_num_0"],
  60. [UIImage imageNamed:@"gift_vip_num_1"],
  61. [UIImage imageNamed:@"gift_vip_num_2"],
  62. [UIImage imageNamed:@"gift_vip_num_3"],
  63. [UIImage imageNamed:@"gift_vip_num_4"],
  64. [UIImage imageNamed:@"gift_vip_num_5"],
  65. [UIImage imageNamed:@"gift_vip_num_6"],
  66. [UIImage imageNamed:@"gift_vip_num_7"],
  67. [UIImage imageNamed:@"gift_vip_num_8"],
  68. [UIImage imageNamed:@"gift_vip_num_9"],
  69. ];
  70. } else {
  71. self.numberImages = @[
  72. [UIImage imageNamed:@"gift_common_num_0"],
  73. [UIImage imageNamed:@"gift_common_num_1"],
  74. [UIImage imageNamed:@"gift_common_num_2"],
  75. [UIImage imageNamed:@"gift_common_num_3"],
  76. [UIImage imageNamed:@"gift_common_num_4"],
  77. [UIImage imageNamed:@"gift_common_num_5"],
  78. [UIImage imageNamed:@"gift_common_num_6"],
  79. [UIImage imageNamed:@"gift_common_num_7"],
  80. [UIImage imageNamed:@"gift_common_num_8"],
  81. [UIImage imageNamed:@"gift_common_num_9"],
  82. ];
  83. }
  84. // 添加布局完成后的回调
  85. [self setNeedsLayout];
  86. [self layoutIfNeeded];
  87. }
  88. - (void)prepareAnimations {
  89. // 先删除旧数据
  90. for (CALayer *layer in _scrollLayers) {
  91. [layer removeFromSuperlayer];
  92. }
  93. [_numbersText removeAllObjects];
  94. [_scrollLayers removeAllObjects];
  95. [_scrollViews removeAllObjects];
  96. // 配置新的数据和UI
  97. [self configNumbersText];
  98. [self configScrollLayers];
  99. }
  100. - (void)configNumbersText {
  101. NSString *numberStr = [_number stringValue];
  102. // 如果 number 长度小于 最小长度就补0
  103. // 这里需要注意一下 minLength 和 length 都是NSUInteger类型 如果相减得负数的话会有问题
  104. for (NSInteger i = 0; i < (NSInteger)self.minLength - (NSInteger)numberStr.length; i++) {
  105. [_numbersText addObject:@"0"];
  106. }
  107. // 取出 number 各位数
  108. for (NSUInteger i = 0; i < numberStr.length; i++) {
  109. [_numbersText addObject:[numberStr substringWithRange:NSMakeRange(i, 1)]];
  110. }
  111. }
  112. - (void)configScrollLayers {
  113. // 确保视图有宽度
  114. if (CGRectGetWidth(self.frame) <= 0) {
  115. return;
  116. }
  117. // 平均分配宽度
  118. CGFloat width = CGRectGetWidth(self.frame) / _numbersText.count;
  119. CGFloat height = CGRectGetHeight(self.frame);
  120. // 创建和配置 scrollLayer
  121. for (NSUInteger i = 0; i < _numbersText.count; i++) {
  122. CAScrollLayer *layer = [CAScrollLayer layer];
  123. layer.frame = CGRectMake(i*width, 0, width, height);
  124. [_scrollLayers addObject:layer];
  125. [self.layer addSublayer:layer];
  126. NSString *numberText = _numbersText[i];
  127. [self configScrollLayer:layer numberText:numberText];
  128. }
  129. }
  130. - (void)configScrollLayer:(CAScrollLayer *)layer numberText:(NSString *)numberText {
  131. NSInteger number = [numberText integerValue];
  132. NSMutableArray *scrollNumbers = [NSMutableArray array];
  133. for (NSInteger i = 0; i < self.density + 1; i++) {
  134. [scrollNumbers addObject:[NSString stringWithFormat:@"%u", (unsigned int)((number+i) % 10)]];
  135. }
  136. [scrollNumbers addObject:numberText];
  137. __block CGFloat height = 0;
  138. [scrollNumbers enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSString *text, NSUInteger idx, BOOL * _Nonnull stop) {
  139. UIImageView *imageView = [self createImageViewForNumber:[text integerValue]];
  140. // 设置固定的显示大小
  141. CGFloat width = CGRectGetWidth(layer.frame);
  142. CGFloat displayHeight = CGRectGetHeight(layer.frame);
  143. // 确保图片完全填充可用空间
  144. imageView.frame = CGRectMake(0, height, width, displayHeight);
  145. imageView.contentMode = UIViewContentModeScaleAspectFit;
  146. [layer addSublayer:imageView.layer];
  147. [_scrollViews addObject:imageView];
  148. height = CGRectGetMaxY(imageView.frame);
  149. }];
  150. }
  151. - (UIImageView *)createImageViewForNumber:(NSInteger)number {
  152. UIImageView *imageView = [[UIImageView alloc] init];
  153. imageView.contentMode = UIViewContentModeScaleAspectFit;
  154. if (self.numberImages && number >= 0 && number < self.numberImages.count) {
  155. imageView.image = self.numberImages[number];
  156. }
  157. return imageView;
  158. }
  159. - (void)createAnimations {
  160. NSString *currentNumberStr = [_number stringValue];
  161. NSString *previousNumberStr = [_previousNumber stringValue];
  162. // 补齐前导零,使两个数字字符串长度相同
  163. while (currentNumberStr.length < self.minLength) {
  164. currentNumberStr = [@"0" stringByAppendingString:currentNumberStr];
  165. }
  166. while (previousNumberStr.length < self.minLength) {
  167. previousNumberStr = [@"0" stringByAppendingString:previousNumberStr];
  168. }
  169. // 第一个需要动画的layer的动画持续时间
  170. NSTimeInterval duration = self.duration - ((_numbersText.count-1) * self.durationOffset);
  171. for (NSUInteger i = 0; i < _scrollLayers.count; i++) {
  172. CALayer *layer = _scrollLayers[i];
  173. // 检查当前位置的数字是否发生变化
  174. BOOL shouldAnimate = YES;
  175. if (_previousNumber != nil) {
  176. NSInteger currentIndex = currentNumberStr.length - _scrollLayers.count + i;
  177. NSInteger previousIndex = previousNumberStr.length - _scrollLayers.count + i;
  178. if (currentIndex >= 0 && previousIndex >= 0) {
  179. unichar currentDigit = [currentNumberStr characterAtIndex:currentIndex];
  180. unichar previousDigit = [previousNumberStr characterAtIndex:previousIndex];
  181. shouldAnimate = (currentDigit != previousDigit);
  182. }
  183. }
  184. if (shouldAnimate) {
  185. CGFloat maxY = [[layer.sublayers lastObject] frame].origin.y;
  186. CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"sublayerTransform.translation.y"];
  187. animation.duration = duration;
  188. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  189. if (self.isAscending) {
  190. animation.fromValue = @0;
  191. animation.toValue = [NSNumber numberWithFloat:-maxY];
  192. } else {
  193. animation.fromValue = [NSNumber numberWithFloat:-maxY];
  194. animation.toValue = @0;
  195. }
  196. [layer addAnimation:animation forKey:@"MSNumberScrollAnimatedView"];
  197. }
  198. duration += self.durationOffset;
  199. }
  200. }
  201. #pragma mark - Setter
  202. - (void)setNumber:(NSNumber *)number {
  203. _previousNumber = _number; // 保存当前数字作为上一次的数字
  204. _number = number;
  205. // 准备动画
  206. [self prepareAnimations];
  207. }
  208. @end