XYScrollNumberLabel.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. //
  2. // XYScrollNumberLabel.m
  3. // Timi
  4. //
  5. // Created by 翟玉磊 on 2021/11/29.
  6. //
  7. #import "XYScrollNumberLabel.h"
  8. #import "XYScrollNumberImageView.h"
  9. typedef enum : NSUInteger {
  10. ScrollAnimationDirectionUp,
  11. ScrollAnimationDirectionDown,
  12. } ScrollAnimationDirection;
  13. static const NSUInteger numberCellLineCount = 11;
  14. static const CGFloat bufferModulus = 0.7f;
  15. static const CGFloat normalModulus = 0.3f;
  16. static NSString * const keyStartDuration = @"keyStartDuration";
  17. static NSString * const keyStartDelay = @"keyStartDelay";
  18. static NSString * const keyCycleDuration = @"keyCycleDuration";
  19. static NSString * const keyEndDuration = @"keyEndDuration";
  20. static NSString * const keyRepeatCount = @"keyRepeatCount";
  21. static NSString * const keyDisplayNumber = @"keyDisplayNumber";
  22. //task key
  23. static NSString * const keyTaskDisplayNumber = @"keyTaskDisplayNumber";
  24. static NSString * const keyTaskChangeNumber = @"keyTaskChangeNumber";
  25. static NSString * const keyTaskInterval = @"keyTaskInterval";
  26. @interface XYScrollNumberLabel ()
  27. @property (nonatomic, assign) BOOL isNeedMark;
  28. @property (nonatomic, strong) UIImageView *markLabel;
  29. @property (nonatomic, assign) CGSize imageSize;
  30. @property (nonatomic, assign, readwrite) NSInteger displayedNumber;
  31. @property (nonatomic, assign) NSInteger rowNumber;
  32. @property (nonatomic, strong) NSMutableArray *cellArray;
  33. @property (nonatomic, assign) CGFloat cellWidth;
  34. @property (nonatomic, assign) CGFloat cellHeight;
  35. @property (nonatomic, strong) NSMutableArray *taskArray;
  36. @property (nonatomic, assign) BOOL isAnimating;
  37. @property (nonatomic, assign) NSInteger finishedAnimationCount;
  38. @end
  39. @implementation XYScrollNumberLabel
  40. #pragma mark - Public
  41. - (instancetype)initImageNumberLabelWithNeewMark:(BOOL)needMark imageSize:(CGSize)imageSize {
  42. return [self initImageNumberLabelWithNeewMark:needMark imageSize:imageSize defaultNumber:0];
  43. }
  44. - (instancetype)initImageNumberLabelWithNeewMark:(BOOL)needMark imageSize:(CGSize)imageSize defaultNumber:(NSInteger)defaultNumber {
  45. if (self = [super init]) {
  46. self.isNeedMark = needMark;
  47. self.imageSize = imageSize;
  48. self.displayedNumber = defaultNumber;
  49. [self commonInit];
  50. }
  51. return self;
  52. }
  53. - (void)commonInit {
  54. [self initValue];
  55. [self initCells];
  56. [self initParent];
  57. }
  58. - (void)initValue {
  59. self.isAnimating = NO;
  60. // cell的高宽
  61. self.cellWidth = self.imageSize.width;
  62. self.cellHeight = self.imageSize.height * numberCellLineCount;
  63. // 计算列数
  64. self.rowNumber = [self calculateRowNumber:self.displayedNumber];
  65. }
  66. - (void)initCells {
  67. // 用于存储所有cell
  68. self.cellArray = [NSMutableArray array];
  69. // 拿到每个位数上显示的数字储存到一个数组中
  70. NSArray *displayNumberArray = [self getCellDisplayNumberWithNumber:self.displayedNumber];
  71. // 初始化每个cell
  72. for (NSInteger i = 0; i < self.rowNumber; i++) {
  73. XYScrollNumberImageView *label = [[XYScrollNumberImageView alloc] initWitImageSize:self.imageSize];
  74. label.frame = CGRectMake((self.rowNumber - 1 - i) * self.cellWidth + (self.isNeedMark?12.0f:0.0f), 0, self.cellWidth, self.cellHeight);
  75. NSNumber *displayNum = [displayNumberArray objectAtIndex:i];
  76. [label changeToNumber:displayNum.integerValue lineCount:numberCellLineCount];
  77. [self.cellArray addObject:label];
  78. }
  79. }
  80. - (void)initParent {
  81. self.bounds = CGRectMake(0, 0, self.rowNumber * self.cellWidth + (self.isNeedMark?12.0f:0.0f), self.cellHeight/numberCellLineCount);
  82. self.backgroundColor = UIColor.clearColor;
  83. self.layer.masksToBounds = YES;
  84. [self layoutCell:self.rowNumber animation:YES];
  85. }
  86. - (void)layoutCell:(NSInteger)rowNumber animation:(BOOL)animation {
  87. // 选移除所有子view
  88. for (UIView *view in self.subviews) {
  89. [view removeFromSuperview];
  90. }
  91. // 添加cell
  92. for (UIView *cell in self.cellArray) {
  93. [self addSubview:cell];
  94. }
  95. // 添加标签
  96. [self addMarkLabel];
  97. if (rowNumber == self.rowNumber) {
  98. return;
  99. }
  100. // 用动画重新排布所有cell
  101. WeakSelf
  102. [UIView animateWithDuration:0.2 * (rowNumber - self.rowNumber) animations:^{
  103. for (int i = 0; i < rowNumber; i++) {
  104. UILabel *cell = [weakSelf.cellArray objectAtIndex:i];
  105. cell.frame = CGRectMake((rowNumber - 1 - i) * weakSelf.cellWidth + (self.isNeedMark?12.0f:0.0f),
  106. cell.frame.origin.y,
  107. weakSelf.cellWidth,
  108. weakSelf.cellHeight);
  109. }
  110. self.frame = CGRectMake(self.frame.origin.x,
  111. self.frame.origin.y,
  112. rowNumber * self.cellWidth + (self.isNeedMark?12.0f:0.0f),
  113. self.cellHeight/numberCellLineCount);
  114. } completion:nil];
  115. }
  116. - (void)addMarkLabel {
  117. [self addSubview:self.markLabel];
  118. self.markLabel.frame = CGRectMake(0, (self.bounds.size.height - 12.0f)/2, 12.0f, 12.0f);
  119. self.markLabel.hidden = !self.isNeedMark;
  120. }
  121. /// 数值改变到指定数值
  122. /// @param targetNumber 要展示的数值
  123. /// @param animated 是否需要动画
  124. - (void)changeToTargetNumber:(NSInteger)targetNumber animated:(BOOL)animated {
  125. [self changeToTargetNumber:targetNumber duration:0 animated:animated];
  126. }
  127. /// 数值改变到指定数值
  128. /// @param targetNumber 要展示的数值
  129. /// @param duration 动画时间,如果设置为0则根据改变值的大小进行计算
  130. /// @param animated 是否需要动画
  131. - (void)changeToTargetNumber:(NSInteger)targetNumber duration:(CGFloat)duration animated:(BOOL)animated {
  132. // 负数不做处理
  133. if (targetNumber < 0) {
  134. return;
  135. }
  136. // 如果传入数字和当前显示相同,则不做处理
  137. if (targetNumber == self.displayedNumber) {
  138. return;
  139. }
  140. if (self.isAnimating) {
  141. if (!self.taskArray) {
  142. self.taskArray = [NSMutableArray array];
  143. }
  144. [self.taskArray addObject:@{keyTaskDisplayNumber:@(targetNumber), keyTaskChangeNumber:@(targetNumber - self.displayedNumber),keyTaskInterval:@(duration)}];
  145. }else {
  146. if (animated) {
  147. [self playAnimationWithChange:targetNumber - self.displayedNumber displayNumber:targetNumber duration:duration];
  148. self.isAnimating = YES;
  149. } else {
  150. if (animated) {
  151. [self playAnimationWithChange:targetNumber - self.displayedNumber displayNumber:targetNumber duration:duration];
  152. self.isAnimating = YES;
  153. } else {
  154. NSArray<NSNumber *> *displayNumbers = [self getCellDisplayNumberWithNumber:targetNumber];
  155. for (int i = 0; i < displayNumbers.count; i++) {
  156. XYScrollNumberImageView *cell = self.cellArray[i];
  157. [cell changeToNumber:displayNumbers[i].integerValue lineCount:numberCellLineCount];
  158. }
  159. }
  160. }
  161. }
  162. self.displayedNumber = targetNumber;
  163. }
  164. - (void)playAnimationWithChange:(NSInteger)changeNumber displayNumber:(NSInteger)displayNumber duration:(CGFloat)duration {
  165. // 改变后的列数
  166. NSInteger nextRowNumber = [self calculateRowNumber:displayNumber];
  167. // 只有当前列数增加时才重新布局
  168. if (nextRowNumber > self.rowNumber) {
  169. [self reInitCell:nextRowNumber];
  170. [self layoutCell:nextRowNumber animation:YES];
  171. self.rowNumber = nextRowNumber;
  172. }
  173. // 储存每一位转圈次数的阵列
  174. NSArray *repeatCountArray = [self getRepeatTimesWithChangeNumber:changeNumber displayNumber:displayNumber];
  175. // 储存每一位将展示的数组
  176. NSArray *willDisplayNums = [self getCellDisplayNumberWithNumber:displayNumber];
  177. // 如果没有设置动画时间,则根据改变的大小来进行计算
  178. if (duration == 0) {
  179. duration = [self getIntervalWithOriginalNumber:displayNumber - changeNumber displayNumber:displayNumber];
  180. }
  181. // 获的滚动方向
  182. ScrollAnimationDirection direction = (changeNumber > 0)? ScrollAnimationDirectionUp : ScrollAnimationDirectionDown;
  183. CGFloat delay = 0.0f;
  184. if (repeatCountArray.count != 0) {
  185. for (NSInteger i = 0; i < repeatCountArray.count; i++) {
  186. NSNumber *repeat = [repeatCountArray objectAtIndex:i];
  187. NSInteger repeatCount = repeat.integerValue;
  188. NSNumber *willDisplayNum = [willDisplayNums objectAtIndex:i];
  189. XYScrollNumberImageView *cell = [self.cellArray objectAtIndex:i];
  190. CGFloat startDuration = 0;
  191. // 当不是一个完整0~9~0回圈时,只进行一个Single动画(这里的动画分为两类:single和multi
  192. if (repeatCount == 0) {
  193. [self makeSingleAnimationWithCell:cell duration:duration delay:delay animationCount:repeatCountArray.count displayNumber:willDisplayNum.integerValue];
  194. }else {
  195. // 当>=一个回圈时,进行multi动画
  196. if (direction == ScrollAnimationDirectionUp) {
  197. // 此处计算三个部分的动画时间
  198. startDuration = duration * (10 - [self getDisplayNumberOfCell:cell]) / ceilf(fabs(changeNumber / pow(10, i)));
  199. CGFloat cycleDuration = duration * 10 / fabs(changeNumber / pow(10, i));
  200. if (repeatCount == 1) {
  201. cycleDuration = 0;
  202. }
  203. CGFloat endDuration = bufferModulus * pow(willDisplayNum.integerValue, 0.3) / (i + 1);
  204. NSDictionary *attribute = @{keyStartDuration: @(startDuration),
  205. keyStartDelay: @(delay),
  206. keyCycleDuration: @(cycleDuration),
  207. keyEndDuration: @(endDuration),
  208. keyRepeatCount: @(repeatCount - 1),
  209. keyDisplayNumber: willDisplayNum};
  210. [self makeMultiAnimationWithCell:cell direction:direction animationCount:repeatCountArray.count attribute:attribute];
  211. }else if (direction == ScrollAnimationDirectionDown) {
  212. startDuration = duration * ([self getDisplayNumberOfCell:cell] - 0) / ceilf(fabs(changeNumber / pow(10, i)));
  213. CGFloat cycleDuration = duration * 10 / fabs(changeNumber / pow(10, i));
  214. // repeatCount不是真正回圈的次数,是cycle的次数加end部分的1,所以如果repeat为1 真正回圈次数是0
  215. if (repeatCount == 1) {
  216. cycleDuration = 0;
  217. }
  218. CGFloat endDuration = bufferModulus * pow(10 - willDisplayNum.integerValue, 0.3) / (i + 1);
  219. NSDictionary *attribute = @{keyStartDuration: @(startDuration),
  220. keyStartDelay: @(delay),
  221. keyCycleDuration: @(cycleDuration),
  222. keyEndDuration: @(endDuration),
  223. keyRepeatCount: @(repeatCount - 1),
  224. keyDisplayNumber: willDisplayNum};
  225. [self makeMultiAnimationWithCell:cell direction:direction animationCount:repeatCountArray.count attribute:attribute];
  226. }
  227. }
  228. delay = delay + startDuration;
  229. }
  230. }
  231. }
  232. - (void)reInitCell:(NSInteger)rowNumber {
  233. if (rowNumber > self.rowNumber) {
  234. for (NSInteger i = self.rowNumber; i < rowNumber; i++) {
  235. XYScrollNumberImageView *label = [[XYScrollNumberImageView alloc] initWitImageSize:self.imageSize];
  236. label.frame = CGRectMake((self.rowNumber - 1 - i) * self.cellWidth + (self.isNeedMark?12.0f:0.0f), 0, self.cellWidth, self.cellHeight);
  237. [self.cellArray addObject:label];
  238. }
  239. }else {
  240. for (NSInteger i = rowNumber; i < self.rowNumber; i++) {
  241. [self.cellArray removeLastObject];
  242. }
  243. }
  244. }
  245. - (NSArray<NSNumber *> *)getRepeatTimesWithChangeNumber:(NSInteger)change displayNumber:(NSInteger)number {
  246. NSMutableArray *repeatTimesArray = [[NSMutableArray alloc] init];
  247. NSInteger originNumber = number - change;
  248. if (change > 0) {
  249. do {
  250. number = (number / 10) * 10;
  251. originNumber = (originNumber / 10) * 10;
  252. NSNumber *repeat = @((number - originNumber) / 10);
  253. [repeatTimesArray addObject:repeat];
  254. number = number / 10;
  255. originNumber = originNumber / 10;
  256. } while ((number - originNumber) != 0);
  257. }else {
  258. do {
  259. number = (number / 10) * 10;
  260. originNumber = (originNumber / 10) * 10;
  261. NSNumber *repeat = @((originNumber - number) / 10);
  262. [repeatTimesArray addObject:repeat];
  263. number = number / 10;
  264. originNumber = originNumber / 10;
  265. } while ((originNumber - number) != 0);
  266. }
  267. return repeatTimesArray;
  268. }
  269. - (void)checkTaskArrayWithAnimationCount:(NSInteger)count {
  270. self.finishedAnimationCount++;
  271. if (self.finishedAnimationCount == count) {
  272. self.finishedAnimationCount = 0;
  273. if (self.taskArray.count != 0) {
  274. NSDictionary *task = [self.taskArray objectAtIndex:0];
  275. [self.taskArray removeObject:task];
  276. NSNumber *displayNumber = [task objectForKey:keyTaskDisplayNumber];
  277. NSNumber *changeNumber = [task objectForKey:keyTaskChangeNumber];
  278. NSNumber *interval = [task objectForKey:keyTaskInterval];
  279. [self playAnimationWithChange:changeNumber.integerValue displayNumber:displayNumber.integerValue duration:interval.floatValue];
  280. }else {
  281. self.isAnimating = NO;
  282. }
  283. }
  284. }
  285. #pragma mark - Method
  286. /// 根据目标值计算列数
  287. - (NSInteger)calculateRowNumber:(NSInteger)number {
  288. NSInteger rowNumber = 1;
  289. while ((number = number / 10) != 0) {
  290. rowNumber++;
  291. }
  292. return rowNumber;
  293. }
  294. /// 将一个数转换为各位上应当显示的数字集合 例如:520 返回@[@0, @2, @5]
  295. /// @param displayNumber 要显示的数值
  296. - (NSArray<NSNumber *> *)getCellDisplayNumberWithNumber:(NSInteger)displayNumber {
  297. NSMutableArray *displayCellNumbers = [[NSMutableArray alloc] init];
  298. NSInteger tmpNumber;
  299. for (NSInteger i = 0; i < self.rowNumber; i++) {
  300. tmpNumber = displayNumber % 10;
  301. NSNumber *number = @(tmpNumber);
  302. [displayCellNumbers addObject:number];
  303. displayNumber = displayNumber / 10;
  304. }
  305. return displayCellNumbers;
  306. }
  307. /// 计算动画总时间
  308. /// @param number 没有改变之前的数值
  309. /// @param displayNumber 即将要展示的数值
  310. - (CGFloat)getIntervalWithOriginalNumber:(NSInteger)number displayNumber:(NSInteger)displayNumber {
  311. NSArray *repeatTimesArray = [self getRepeatTimesWithChangeNumber:displayNumber - number displayNumber:displayNumber];
  312. NSUInteger count = repeatTimesArray.count;
  313. NSInteger tmp1 = displayNumber / (NSInteger)pow(10, count - 1);
  314. NSInteger tmp2 = number / (NSInteger)pow(10, count - 1);
  315. NSLog(@"tmp1:%ld tmp2:%ld", (long)tmp1, (long)tmp2);
  316. NSInteger maxChangeNum = labs(tmp1 % 10 - tmp2 % 10);
  317. return normalModulus * count * maxChangeNum;
  318. }
  319. - (void)makeSingleAnimationWithCell:(XYScrollNumberImageView *)cell duration:(CGFloat)duration delay:(CGFloat)delay animationCount:(NSInteger)count displayNumber:(NSInteger)displayNumber {
  320. [UIView animateWithDuration:duration delay:delay options:UIViewAnimationOptionCurveEaseOut animations:^{
  321. [cell changeToNumber:displayNumber lineCount:numberCellLineCount];
  322. } completion:^(BOOL finished) {
  323. // 当动画结束,检查是否有待执行的动画
  324. [self checkTaskArrayWithAnimationCount:count];
  325. NSLog(@"当次动画完成!");
  326. }];
  327. }
  328. - (void)makeMultiAnimationWithCell:(XYScrollNumberImageView *)cell
  329. direction:(ScrollAnimationDirection)direction
  330. animationCount:(NSInteger)count
  331. attribute:(NSDictionary *)attribute{
  332. NSNumber *startDuration = [attribute objectForKey:keyStartDuration];
  333. NSNumber *cycleDuration = [attribute objectForKey:keyCycleDuration];
  334. NSNumber *endDuration = [attribute objectForKey:keyEndDuration];
  335. NSNumber *repeatCount = [attribute objectForKey:keyRepeatCount];
  336. NSNumber *willDisplayNum = [attribute objectForKey:keyDisplayNumber];
  337. NSNumber *startDelay = [attribute objectForKey:keyStartDelay];
  338. [UIView animateWithDuration:startDuration.floatValue delay:startDelay.floatValue options:UIViewAnimationOptionCurveEaseIn animations:^{
  339. // 这是开始部分的动画
  340. [cell changeToNumber:(direction == ScrollAnimationDirectionUp)?10:0 lineCount:numberCellLineCount];
  341. } completion:^(BOOL finished) {
  342. NSLog(@"start animation finish!");
  343. // 开始动画结束后将cell归位到回圈开始的地方
  344. [cell changeToNumber:(direction == ScrollAnimationDirectionUp)?0:10 lineCount:numberCellLineCount];
  345. if (cycleDuration.floatValue == 0) {
  346. // 当回圈次数为0,直接执行结束部分的动画
  347. [UIView animateWithDuration:endDuration.floatValue delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
  348. [cell changeToNumber:willDisplayNum.integerValue lineCount:numberCellLineCount];
  349. } completion:^(BOOL finished) {
  350. [self checkTaskArrayWithAnimationCount:count];
  351. NSLog(@"end animation finish!");
  352. }];
  353. }else {
  354. // 否则进入回圈动画
  355. [UIView animateWithDuration:cycleDuration.floatValue delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionRepeat animations:^{
  356. [UIView setAnimationRepeatCount:repeatCount.integerValue];
  357. switch (direction) {
  358. case ScrollAnimationDirectionUp:
  359. [cell changeToNumber:10 lineCount:numberCellLineCount];
  360. break;
  361. case ScrollAnimationDirectionDown:
  362. [cell changeToNumber:0 lineCount:numberCellLineCount];
  363. break;
  364. default:
  365. break;
  366. }
  367. } completion:^(BOOL finished) {
  368. NSLog(@"cycle animation finish!");
  369. [cell changeToNumber:(direction == ScrollAnimationDirectionUp)?0:10 lineCount:numberCellLineCount];
  370. // 这是回圈后的结束动画
  371. [UIView animateWithDuration:endDuration.floatValue delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
  372. [cell changeToNumber:willDisplayNum.integerValue lineCount:numberCellLineCount];
  373. } completion:^(BOOL finished) {
  374. [self checkTaskArrayWithAnimationCount:count];
  375. NSLog(@"end animation finish!");
  376. }];
  377. }];
  378. }
  379. }];
  380. }
  381. /// 获得指定cell展示的数值
  382. - (NSInteger)getDisplayNumberOfCell:(XYScrollNumberImageView *)cell {
  383. CGFloat y = cell.frame.origin.y;
  384. CGFloat tmpNumber = (- (y * numberCellLineCount / self.cellHeight));
  385. NSInteger displayNumber = (NSInteger)roundf(tmpNumber);
  386. return displayNumber;
  387. }
  388. - (UIImageView *)markLabel {
  389. if (_markLabel == nil) {
  390. _markLabel = [[UIImageView alloc] init];
  391. _markLabel.image = ImageNamed(@"xy_gift_num_×");
  392. }
  393. return _markLabel;
  394. }
  395. @end