123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- //
- // XYScrollNumberLabel.m
- // Timi
- //
- // Created by 翟玉磊 on 2021/11/29.
- //
- #import "XYScrollNumberLabel.h"
- #import "XYScrollNumberImageView.h"
- typedef enum : NSUInteger {
- ScrollAnimationDirectionUp,
- ScrollAnimationDirectionDown,
- } ScrollAnimationDirection;
- static const NSUInteger numberCellLineCount = 11;
- static const CGFloat bufferModulus = 0.7f;
- static const CGFloat normalModulus = 0.3f;
- static NSString * const keyStartDuration = @"keyStartDuration";
- static NSString * const keyStartDelay = @"keyStartDelay";
- static NSString * const keyCycleDuration = @"keyCycleDuration";
- static NSString * const keyEndDuration = @"keyEndDuration";
- static NSString * const keyRepeatCount = @"keyRepeatCount";
- static NSString * const keyDisplayNumber = @"keyDisplayNumber";
- //task key
- static NSString * const keyTaskDisplayNumber = @"keyTaskDisplayNumber";
- static NSString * const keyTaskChangeNumber = @"keyTaskChangeNumber";
- static NSString * const keyTaskInterval = @"keyTaskInterval";
- @interface XYScrollNumberLabel ()
- @property (nonatomic, assign) BOOL isNeedMark;
- @property (nonatomic, strong) UIImageView *markLabel;
- @property (nonatomic, assign) CGSize imageSize;
- @property (nonatomic, assign, readwrite) NSInteger displayedNumber;
- @property (nonatomic, assign) NSInteger rowNumber;
- @property (nonatomic, strong) NSMutableArray *cellArray;
- @property (nonatomic, assign) CGFloat cellWidth;
- @property (nonatomic, assign) CGFloat cellHeight;
- @property (nonatomic, strong) NSMutableArray *taskArray;
- @property (nonatomic, assign) BOOL isAnimating;
- @property (nonatomic, assign) NSInteger finishedAnimationCount;
- @end
- @implementation XYScrollNumberLabel
- #pragma mark - Public
- - (instancetype)initImageNumberLabelWithNeewMark:(BOOL)needMark imageSize:(CGSize)imageSize {
- return [self initImageNumberLabelWithNeewMark:needMark imageSize:imageSize defaultNumber:0];
- }
- - (instancetype)initImageNumberLabelWithNeewMark:(BOOL)needMark imageSize:(CGSize)imageSize defaultNumber:(NSInteger)defaultNumber {
- if (self = [super init]) {
- self.isNeedMark = needMark;
- self.imageSize = imageSize;
- self.displayedNumber = defaultNumber;
- [self commonInit];
- }
- return self;
- }
- - (void)commonInit {
- [self initValue];
- [self initCells];
- [self initParent];
- }
- - (void)initValue {
- self.isAnimating = NO;
- // cell的高宽
- self.cellWidth = self.imageSize.width;
- self.cellHeight = self.imageSize.height * numberCellLineCount;
- // 计算列数
- self.rowNumber = [self calculateRowNumber:self.displayedNumber];
- }
- - (void)initCells {
- // 用于存储所有cell
- self.cellArray = [NSMutableArray array];
- // 拿到每个位数上显示的数字储存到一个数组中
- NSArray *displayNumberArray = [self getCellDisplayNumberWithNumber:self.displayedNumber];
- // 初始化每个cell
- for (NSInteger i = 0; i < self.rowNumber; i++) {
- XYScrollNumberImageView *label = [[XYScrollNumberImageView alloc] initWitImageSize:self.imageSize];
- label.frame = CGRectMake((self.rowNumber - 1 - i) * self.cellWidth + (self.isNeedMark?12.0f:0.0f), 0, self.cellWidth, self.cellHeight);
- NSNumber *displayNum = [displayNumberArray objectAtIndex:i];
- [label changeToNumber:displayNum.integerValue lineCount:numberCellLineCount];
- [self.cellArray addObject:label];
- }
- }
- - (void)initParent {
- self.bounds = CGRectMake(0, 0, self.rowNumber * self.cellWidth + (self.isNeedMark?12.0f:0.0f), self.cellHeight/numberCellLineCount);
- self.backgroundColor = UIColor.clearColor;
- self.layer.masksToBounds = YES;
-
- [self layoutCell:self.rowNumber animation:YES];
- }
- - (void)layoutCell:(NSInteger)rowNumber animation:(BOOL)animation {
- // 选移除所有子view
- for (UIView *view in self.subviews) {
- [view removeFromSuperview];
- }
- // 添加cell
- for (UIView *cell in self.cellArray) {
- [self addSubview:cell];
- }
-
- // 添加标签
- [self addMarkLabel];
-
- if (rowNumber == self.rowNumber) {
- return;
- }
-
- // 用动画重新排布所有cell
- WeakSelf
- [UIView animateWithDuration:0.2 * (rowNumber - self.rowNumber) animations:^{
- for (int i = 0; i < rowNumber; i++) {
- UILabel *cell = [weakSelf.cellArray objectAtIndex:i];
- cell.frame = CGRectMake((rowNumber - 1 - i) * weakSelf.cellWidth + (self.isNeedMark?12.0f:0.0f),
- cell.frame.origin.y,
- weakSelf.cellWidth,
- weakSelf.cellHeight);
- }
- self.frame = CGRectMake(self.frame.origin.x,
- self.frame.origin.y,
- rowNumber * self.cellWidth + (self.isNeedMark?12.0f:0.0f),
- self.cellHeight/numberCellLineCount);
- } completion:nil];
- }
- - (void)addMarkLabel {
- [self addSubview:self.markLabel];
- self.markLabel.frame = CGRectMake(0, (self.bounds.size.height - 12.0f)/2, 12.0f, 12.0f);
- self.markLabel.hidden = !self.isNeedMark;
- }
- /// 数值改变到指定数值
- /// @param targetNumber 要展示的数值
- /// @param animated 是否需要动画
- - (void)changeToTargetNumber:(NSInteger)targetNumber animated:(BOOL)animated {
- [self changeToTargetNumber:targetNumber duration:0 animated:animated];
- }
- /// 数值改变到指定数值
- /// @param targetNumber 要展示的数值
- /// @param duration 动画时间,如果设置为0则根据改变值的大小进行计算
- /// @param animated 是否需要动画
- - (void)changeToTargetNumber:(NSInteger)targetNumber duration:(CGFloat)duration animated:(BOOL)animated {
- // 负数不做处理
- if (targetNumber < 0) {
- return;
- }
- // 如果传入数字和当前显示相同,则不做处理
- if (targetNumber == self.displayedNumber) {
- return;
- }
- if (self.isAnimating) {
- if (!self.taskArray) {
- self.taskArray = [NSMutableArray array];
- }
- [self.taskArray addObject:@{keyTaskDisplayNumber:@(targetNumber), keyTaskChangeNumber:@(targetNumber - self.displayedNumber),keyTaskInterval:@(duration)}];
- }else {
- if (animated) {
- [self playAnimationWithChange:targetNumber - self.displayedNumber displayNumber:targetNumber duration:duration];
- self.isAnimating = YES;
- } else {
- if (animated) {
- [self playAnimationWithChange:targetNumber - self.displayedNumber displayNumber:targetNumber duration:duration];
- self.isAnimating = YES;
- } else {
- NSArray<NSNumber *> *displayNumbers = [self getCellDisplayNumberWithNumber:targetNumber];
- for (int i = 0; i < displayNumbers.count; i++) {
- XYScrollNumberImageView *cell = self.cellArray[i];
- [cell changeToNumber:displayNumbers[i].integerValue lineCount:numberCellLineCount];
- }
- }
- }
- }
- self.displayedNumber = targetNumber;
- }
- - (void)playAnimationWithChange:(NSInteger)changeNumber displayNumber:(NSInteger)displayNumber duration:(CGFloat)duration {
- // 改变后的列数
- NSInteger nextRowNumber = [self calculateRowNumber:displayNumber];
- // 只有当前列数增加时才重新布局
- if (nextRowNumber > self.rowNumber) {
- [self reInitCell:nextRowNumber];
- [self layoutCell:nextRowNumber animation:YES];
- self.rowNumber = nextRowNumber;
- }
- // 储存每一位转圈次数的阵列
- NSArray *repeatCountArray = [self getRepeatTimesWithChangeNumber:changeNumber displayNumber:displayNumber];
- // 储存每一位将展示的数组
- NSArray *willDisplayNums = [self getCellDisplayNumberWithNumber:displayNumber];
- // 如果没有设置动画时间,则根据改变的大小来进行计算
- if (duration == 0) {
- duration = [self getIntervalWithOriginalNumber:displayNumber - changeNumber displayNumber:displayNumber];
- }
- // 获的滚动方向
- ScrollAnimationDirection direction = (changeNumber > 0)? ScrollAnimationDirectionUp : ScrollAnimationDirectionDown;
-
- CGFloat delay = 0.0f;
-
- if (repeatCountArray.count != 0) {
- for (NSInteger i = 0; i < repeatCountArray.count; i++) {
- NSNumber *repeat = [repeatCountArray objectAtIndex:i];
- NSInteger repeatCount = repeat.integerValue;
- NSNumber *willDisplayNum = [willDisplayNums objectAtIndex:i];
- XYScrollNumberImageView *cell = [self.cellArray objectAtIndex:i];
- CGFloat startDuration = 0;
- // 当不是一个完整0~9~0回圈时,只进行一个Single动画(这里的动画分为两类:single和multi
- if (repeatCount == 0) {
- [self makeSingleAnimationWithCell:cell duration:duration delay:delay animationCount:repeatCountArray.count displayNumber:willDisplayNum.integerValue];
- }else {
- // 当>=一个回圈时,进行multi动画
- if (direction == ScrollAnimationDirectionUp) {
- // 此处计算三个部分的动画时间
- startDuration = duration * (10 - [self getDisplayNumberOfCell:cell]) / ceilf(fabs(changeNumber / pow(10, i)));
- CGFloat cycleDuration = duration * 10 / fabs(changeNumber / pow(10, i));
- if (repeatCount == 1) {
- cycleDuration = 0;
- }
- CGFloat endDuration = bufferModulus * pow(willDisplayNum.integerValue, 0.3) / (i + 1);
- NSDictionary *attribute = @{keyStartDuration: @(startDuration),
- keyStartDelay: @(delay),
- keyCycleDuration: @(cycleDuration),
- keyEndDuration: @(endDuration),
- keyRepeatCount: @(repeatCount - 1),
- keyDisplayNumber: willDisplayNum};
- [self makeMultiAnimationWithCell:cell direction:direction animationCount:repeatCountArray.count attribute:attribute];
- }else if (direction == ScrollAnimationDirectionDown) {
- startDuration = duration * ([self getDisplayNumberOfCell:cell] - 0) / ceilf(fabs(changeNumber / pow(10, i)));
- CGFloat cycleDuration = duration * 10 / fabs(changeNumber / pow(10, i));
- // repeatCount不是真正回圈的次数,是cycle的次数加end部分的1,所以如果repeat为1 真正回圈次数是0
- if (repeatCount == 1) {
- cycleDuration = 0;
- }
- CGFloat endDuration = bufferModulus * pow(10 - willDisplayNum.integerValue, 0.3) / (i + 1);
- NSDictionary *attribute = @{keyStartDuration: @(startDuration),
- keyStartDelay: @(delay),
- keyCycleDuration: @(cycleDuration),
- keyEndDuration: @(endDuration),
- keyRepeatCount: @(repeatCount - 1),
- keyDisplayNumber: willDisplayNum};
- [self makeMultiAnimationWithCell:cell direction:direction animationCount:repeatCountArray.count attribute:attribute];
- }
- }
- delay = delay + startDuration;
- }
- }
- }
- - (void)reInitCell:(NSInteger)rowNumber {
-
- if (rowNumber > self.rowNumber) {
- for (NSInteger i = self.rowNumber; i < rowNumber; i++) {
- XYScrollNumberImageView *label = [[XYScrollNumberImageView alloc] initWitImageSize:self.imageSize];
- label.frame = CGRectMake((self.rowNumber - 1 - i) * self.cellWidth + (self.isNeedMark?12.0f:0.0f), 0, self.cellWidth, self.cellHeight);
- [self.cellArray addObject:label];
- }
- }else {
- for (NSInteger i = rowNumber; i < self.rowNumber; i++) {
- [self.cellArray removeLastObject];
- }
- }
- }
- - (NSArray<NSNumber *> *)getRepeatTimesWithChangeNumber:(NSInteger)change displayNumber:(NSInteger)number {
- NSMutableArray *repeatTimesArray = [[NSMutableArray alloc] init];
- NSInteger originNumber = number - change;
- if (change > 0) {
- do {
- number = (number / 10) * 10;
- originNumber = (originNumber / 10) * 10;
- NSNumber *repeat = @((number - originNumber) / 10);
- [repeatTimesArray addObject:repeat];
- number = number / 10;
- originNumber = originNumber / 10;
- } while ((number - originNumber) != 0);
- }else {
- do {
- number = (number / 10) * 10;
- originNumber = (originNumber / 10) * 10;
- NSNumber *repeat = @((originNumber - number) / 10);
- [repeatTimesArray addObject:repeat];
- number = number / 10;
- originNumber = originNumber / 10;
- } while ((originNumber - number) != 0);
- }
- return repeatTimesArray;
- }
- - (void)checkTaskArrayWithAnimationCount:(NSInteger)count {
- self.finishedAnimationCount++;
- if (self.finishedAnimationCount == count) {
- self.finishedAnimationCount = 0;
- if (self.taskArray.count != 0) {
- NSDictionary *task = [self.taskArray objectAtIndex:0];
- [self.taskArray removeObject:task];
- NSNumber *displayNumber = [task objectForKey:keyTaskDisplayNumber];
- NSNumber *changeNumber = [task objectForKey:keyTaskChangeNumber];
- NSNumber *interval = [task objectForKey:keyTaskInterval];
- [self playAnimationWithChange:changeNumber.integerValue displayNumber:displayNumber.integerValue duration:interval.floatValue];
- }else {
- self.isAnimating = NO;
- }
- }
- }
- #pragma mark - Method
- /// 根据目标值计算列数
- - (NSInteger)calculateRowNumber:(NSInteger)number {
- NSInteger rowNumber = 1;
- while ((number = number / 10) != 0) {
- rowNumber++;
- }
- return rowNumber;
- }
- /// 将一个数转换为各位上应当显示的数字集合 例如:520 返回@[@0, @2, @5]
- /// @param displayNumber 要显示的数值
- - (NSArray<NSNumber *> *)getCellDisplayNumberWithNumber:(NSInteger)displayNumber {
- NSMutableArray *displayCellNumbers = [[NSMutableArray alloc] init];
- NSInteger tmpNumber;
- for (NSInteger i = 0; i < self.rowNumber; i++) {
- tmpNumber = displayNumber % 10;
- NSNumber *number = @(tmpNumber);
- [displayCellNumbers addObject:number];
- displayNumber = displayNumber / 10;
- }
- return displayCellNumbers;
- }
- /// 计算动画总时间
- /// @param number 没有改变之前的数值
- /// @param displayNumber 即将要展示的数值
- - (CGFloat)getIntervalWithOriginalNumber:(NSInteger)number displayNumber:(NSInteger)displayNumber {
- NSArray *repeatTimesArray = [self getRepeatTimesWithChangeNumber:displayNumber - number displayNumber:displayNumber];
- NSUInteger count = repeatTimesArray.count;
- NSInteger tmp1 = displayNumber / (NSInteger)pow(10, count - 1);
- NSInteger tmp2 = number / (NSInteger)pow(10, count - 1);
- NSLog(@"tmp1:%ld tmp2:%ld", (long)tmp1, (long)tmp2);
- NSInteger maxChangeNum = labs(tmp1 % 10 - tmp2 % 10);
- return normalModulus * count * maxChangeNum;
- }
- - (void)makeSingleAnimationWithCell:(XYScrollNumberImageView *)cell duration:(CGFloat)duration delay:(CGFloat)delay animationCount:(NSInteger)count displayNumber:(NSInteger)displayNumber {
- [UIView animateWithDuration:duration delay:delay options:UIViewAnimationOptionCurveEaseOut animations:^{
- [cell changeToNumber:displayNumber lineCount:numberCellLineCount];
- } completion:^(BOOL finished) {
- // 当动画结束,检查是否有待执行的动画
- [self checkTaskArrayWithAnimationCount:count];
- NSLog(@"当次动画完成!");
- }];
- }
- - (void)makeMultiAnimationWithCell:(XYScrollNumberImageView *)cell
- direction:(ScrollAnimationDirection)direction
- animationCount:(NSInteger)count
- attribute:(NSDictionary *)attribute{
- NSNumber *startDuration = [attribute objectForKey:keyStartDuration];
- NSNumber *cycleDuration = [attribute objectForKey:keyCycleDuration];
- NSNumber *endDuration = [attribute objectForKey:keyEndDuration];
- NSNumber *repeatCount = [attribute objectForKey:keyRepeatCount];
- NSNumber *willDisplayNum = [attribute objectForKey:keyDisplayNumber];
- NSNumber *startDelay = [attribute objectForKey:keyStartDelay];
- [UIView animateWithDuration:startDuration.floatValue delay:startDelay.floatValue options:UIViewAnimationOptionCurveEaseIn animations:^{
- // 这是开始部分的动画
- [cell changeToNumber:(direction == ScrollAnimationDirectionUp)?10:0 lineCount:numberCellLineCount];
- } completion:^(BOOL finished) {
- NSLog(@"start animation finish!");
- // 开始动画结束后将cell归位到回圈开始的地方
- [cell changeToNumber:(direction == ScrollAnimationDirectionUp)?0:10 lineCount:numberCellLineCount];
- if (cycleDuration.floatValue == 0) {
- // 当回圈次数为0,直接执行结束部分的动画
- [UIView animateWithDuration:endDuration.floatValue delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
- [cell changeToNumber:willDisplayNum.integerValue lineCount:numberCellLineCount];
- } completion:^(BOOL finished) {
- [self checkTaskArrayWithAnimationCount:count];
- NSLog(@"end animation finish!");
- }];
- }else {
- // 否则进入回圈动画
- [UIView animateWithDuration:cycleDuration.floatValue delay:0 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionRepeat animations:^{
- [UIView setAnimationRepeatCount:repeatCount.integerValue];
- switch (direction) {
- case ScrollAnimationDirectionUp:
- [cell changeToNumber:10 lineCount:numberCellLineCount];
- break;
- case ScrollAnimationDirectionDown:
- [cell changeToNumber:0 lineCount:numberCellLineCount];
- break;
- default:
- break;
- }
- } completion:^(BOOL finished) {
- NSLog(@"cycle animation finish!");
- [cell changeToNumber:(direction == ScrollAnimationDirectionUp)?0:10 lineCount:numberCellLineCount];
- // 这是回圈后的结束动画
- [UIView animateWithDuration:endDuration.floatValue delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
- [cell changeToNumber:willDisplayNum.integerValue lineCount:numberCellLineCount];
- } completion:^(BOOL finished) {
- [self checkTaskArrayWithAnimationCount:count];
- NSLog(@"end animation finish!");
- }];
- }];
- }
- }];
- }
- /// 获得指定cell展示的数值
- - (NSInteger)getDisplayNumberOfCell:(XYScrollNumberImageView *)cell {
- CGFloat y = cell.frame.origin.y;
- CGFloat tmpNumber = (- (y * numberCellLineCount / self.cellHeight));
- NSInteger displayNumber = (NSInteger)roundf(tmpNumber);
- return displayNumber;
- }
- - (UIImageView *)markLabel {
- if (_markLabel == nil) {
- _markLabel = [[UIImageView alloc] init];
- _markLabel.image = ImageNamed(@"xy_gift_num_×");
- }
- return _markLabel;
- }
- @end
|