.h文件
#import <UIKit/UIKit.h>
@interface HAScrollNumberLabel : UIView
/// size改变回调
@property (nonatomic, copy) void(^didChangeSizeHandler)(CGSize size);
/// 当前显示值
@property (nonatomic, strong, readonly) NSNumber *currentNumber;
/// 初始化
- (instancetype)initWithNumber:(NSNumber *)number font:(UIFont *)font textColor:(UIColor *)textColor rowNumber:(NSUInteger)rowNumber;
/// 改变显示值
- (void)changeToNumber:(NSNumber *)number animated:(BOOL)animated;
@end
.m文件
#import "HAScrollNumberLabel.h"
@interface HAAnimationTask : NSObject
/// 目标显示值
@property (nonatomic, assign) NSInteger targetNumber;
/// 改变的数值
@property (nonatomic, assign) NSInteger changeValue;
@end
@implementation HAAnimationTask
@end
static const NSUInteger numberCellLineCount = 21;
static NSString * const numberCellText = @"0\n9\n8\n7\n6\n5\n4\n3\n2\n1\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0";
@interface HAScrollNumberLabel ()
/// 当前显示值
@property (nonatomic, strong, readwrite) NSNumber *currentNumber;
/// 目标显示值
@property (nonatomic, strong) NSNumber *targetNumber;
/// 标签数组
@property (nonatomic, strong) NSMutableArray<UILabel *> *cellArray;
/// 当前列数
@property (nonatomic, assign) NSUInteger rowNumber;
/// 最大列数,10位
@property (nonatomic, assign) NSUInteger maxRowNumber;
/// 最小列表
@property (nonatomic, assign) NSInteger minRowNumber;
/// 列宽
@property (nonatomic, assign) CGFloat cellWidth;
/// 行高
@property (nonatomic, assign) CGFloat numberCellHeight;
/// 字体颜色
@property (nonatomic, strong) UIColor *textColor;
/// 字体
@property (nonatomic, strong) UIFont *font;
/// 任务数组
@property (nonatomic, strong) NSMutableArray *taskQueue;
/// 动画计数
@property (nonatomic, assign) NSInteger finishedAnimationCount;
/// 是否正在动画
@property (nonatomic, assign) BOOL isAnimating;
/// 当前的宽度
@property (nonatomic, assign) CGFloat totalWidth;
@end
@implementation HAScrollNumberLabel
/// 初始化
- (instancetype)initWithNumber:(NSNumber *)number font:(UIFont *)font textColor:(UIColor *)textColor rowNumber:(NSUInteger)rowNumber {
self = [super init];
if (self) {
CGSize size = [numberCellText boundingRectWithSize: CGSizeZero options: NSStringDrawingUsesLineFragmentOrigin attributes: @{
NSFontAttributeName: font
} context: nil].size;
_cellWidth = size.width;
_numberCellHeight = size.height;
_targetNumber = number;
_currentNumber = number;
_font = font;
_textColor = textColor;
_isAnimating = NO;
_finishedAnimationCount = 0;
_maxRowNumber = 10;
if (rowNumber > 0 && rowNumber <= _maxRowNumber) {
_rowNumber = rowNumber;
} else {
_rowNumber = [self calculateNumberRow: number.integerValue];
}
_minRowNumber = rowNumber;
_cellArray = [[NSMutableArray alloc] init];
NSArray *displayNumberArray = [self getEachCellValueArrayWithTargetNumber: number.integerValue];
for (NSInteger i = 0; i < _rowNumber; i++) {
UILabel *numberCell = [self makeNumberCell];
numberCell.frame = CGRectMake((_rowNumber - 1 - i) * _cellWidth, 0, _cellWidth, _numberCellHeight);
NSNumber *displayNum = [displayNumberArray objectAtIndex: i];
[self moveNumberCell: numberCell toNumber: displayNum.integerValue];
[self addSubview: numberCell];
[_cellArray addObject: numberCell];
}
self.bounds = CGRectMake(0, 0, _rowNumber * _cellWidth, _numberCellHeight / numberCellLineCount);
self.backgroundColor = [UIColor clearColor];
self.layer.masksToBounds = YES;
}
return self;
}
/// 改变显示值,滚动动画的间隔会自动计算
- (void)changeToNumber:(NSNumber *)number animated:(BOOL)animated {
if ([self calculateNumberRow: number.integerValue] > _maxRowNumber) {
return;
}
if (number.integerValue == _currentNumber.integerValue) {
return;
}
if (_isAnimating) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
HAAnimationTask *task = [[HAAnimationTask alloc] init];
task.targetNumber = number.integerValue;
task.changeValue = number.integerValue - self.targetNumber.integerValue;
@synchronized (self.taskQueue) {
[self.taskQueue removeAllObjects];
[self.taskQueue addObject: task];
}
});
} else {
NSNumber *previousNumber = _targetNumber;
_targetNumber = number;
if (animated) {
[self playAnimationWithChange: number.integerValue - previousNumber.integerValue previousNumber: previousNumber];
_isAnimating = YES;
} else {
NSArray<NSNumber *> *displayNumbers = [self getEachCellValueArrayWithTargetNumber: number.integerValue];
for (int i = 0; i < displayNumbers.count; i++) {
[self moveNumberCell: _cellArray[i] toNumber: displayNumbers[i].integerValue];
}
}
}
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _rowNumber * _cellWidth, _numberCellHeight / numberCellLineCount);
}
/// 更新行数
- (void)updateToRowNumber:(NSInteger)rowNumber {
if (rowNumber == _rowNumber || rowNumber < _minRowNumber) {
return;
}
/// 移除全部
[_cellArray makeObjectsPerformSelector: @selector(removeFromSuperview)];
if (rowNumber > _rowNumber) {
for (NSInteger i = _rowNumber; i < rowNumber; i++) {
UILabel *scrollCell = [self makeNumberCell];
scrollCell.frame = CGRectMake((_rowNumber - 1 - i) * _cellWidth, -_numberCellHeight * 10 / numberCellLineCount, _cellWidth, _numberCellHeight);
[_cellArray addObject: scrollCell];
}
} else {
for (NSInteger i = rowNumber; i < _rowNumber; i++) {
[_cellArray removeLastObject];
}
}
/// 重新添加
for (UILabel *cell in _cellArray) {
[self addSubview: cell];
}
for (int i = 0; i < rowNumber; i++) {
UILabel *cell = [_cellArray objectAtIndex: i];
cell.frame = CGRectMake((rowNumber - 1 - i) * _cellWidth, cell.frame.origin.y, _cellWidth, _numberCellHeight);
}
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, rowNumber * _cellWidth, _numberCellHeight / numberCellLineCount);
_rowNumber = rowNumber;
}
/// 检查动画
- (void)checkTaskArray {
@synchronized (self.taskQueue) {
if (self.taskQueue.count != 0) {
HAAnimationTask *task = [self.taskQueue firstObject];
[self.taskQueue removeObject: task];
NSNumber *previousNumber = self.targetNumber;
self.targetNumber = @(task.targetNumber);
[self playAnimationWithChange: task.changeValue previousNumber: previousNumber];
} else {
self.isAnimating = NO;
NSInteger needNumberRow = [self calculateNumberRow: self.currentNumber.intValue];
if (needNumberRow < self.rowNumber) {
[self updateToRowNumber: needNumberRow];
}
}
}
}
/// 改变动画
- (void)playAnimationWithChange:(NSInteger)changeValue previousNumber:(NSNumber *)previousNumber {
NSInteger targetInteger = _targetNumber.integerValue;
NSInteger targetRowNumber = [self calculateNumberRow: targetInteger];
if (targetRowNumber > _rowNumber) {
[self updateToRowNumber: targetRowNumber];
}
NSArray<NSNumber *> *repeatCountArray = [self getRepeatTimesWithChangeNumber: changeValue targetNumber: targetInteger];
NSArray<NSNumber *> *targetDisplayNums = [self getEachCellValueArrayWithTargetNumber: targetInteger];
CGFloat interval = [self getIntervalWithPreviousNumber: previousNumber.integerValue targetNumber: targetInteger];
if (repeatCountArray.count != 0) {
for (NSInteger i = 0; i < repeatCountArray.count; i++) {
NSInteger willDisplayNum = [targetDisplayNums objectAtIndex: i].integerValue;
UILabel *cell = [_cellArray objectAtIndex: i];
if ([repeatCountArray objectAtIndex: i].integerValue == 0) {
[self makeSingleAnimationWithCell: cell animationCount: repeatCountArray.count displayNumber: willDisplayNum duration: interval];
} else {
CGFloat duration = interval * (10 - [self getValueOfCell: cell]) / ceilf(fabs(changeValue / pow(10, i)));
[self makeMultiAnimationWithCell: cell animationCount: repeatCountArray.count displayNumber: willDisplayNum duration: duration delay: MAX(0, interval - duration)];
}
}
}
}
/// 单列动画
- (void)makeSingleAnimationWithCell:(UILabel *)cell animationCount:(NSInteger)count displayNumber:(NSInteger)displayNumber duration:(CGFloat)duration {
[UIView animateWithDuration: duration delay: 0.0 options: UIViewAnimationOptionCurveEaseOut animations:^{
[self moveNumberCell: cell toNumber: displayNumber];
} completion:^(BOOL finished) {
[self oneAnimationDidFinishedWithTotalCount: count];
}];
}
/// 多列动画
- (void)makeMultiAnimationWithCell:(UILabel *)cell animationCount:(NSInteger)count displayNumber:(NSInteger)displayNumber duration:(NSTimeInterval)duration delay:(NSTimeInterval)delay {
[UIView animateWithDuration: duration delay: 0.0 options: UIViewAnimationOptionCurveEaseIn animations:^{
[self moveNumberCell: cell toNumber: 10];
} completion:^(BOOL finished) {
[self moveNumberCell: cell toNumber: 0];
[UIView animateWithDuration: delay delay: 0 options: UIViewAnimationOptionCurveEaseOut animations:^{
[self moveNumberCell: cell toNumber: displayNumber];
} completion:^(BOOL finished) {
[self oneAnimationDidFinishedWithTotalCount: count];
}];
}];
}
/// 动画结束
- (void)oneAnimationDidFinishedWithTotalCount:(NSInteger)totalCount {
_finishedAnimationCount++;
if (_finishedAnimationCount == totalCount) {
_finishedAnimationCount = 0;
_currentNumber = _targetNumber;
[self checkTaskArray];
}
}
/// size改变
- (void)setFrame:(CGRect)frame {
[super setFrame:frame];
if (_totalWidth != frame.size.width) {
_totalWidth = frame.size.width;
if (_didChangeSizeHandler) {
_didChangeSizeHandler(frame.size);
}
}
}
/// 计算列数
- (NSInteger)calculateNumberRow:(NSInteger)number {
NSInteger numberRow = 1;
while ((number = number / 10) != 0) {
numberRow++;
}
return numberRow;
}
/// 移动数值
- (void)moveNumberCell:(UILabel *)cell toNumber:(NSInteger)number {
CGFloat x = cell.frame.origin.x;
CGFloat floatNumber = abs((int)number);
CGFloat y = -_numberCellHeight / numberCellLineCount * 10 - ((CGFloat)floatNumber / numberCellLineCount) * _numberCellHeight;
cell.frame = CGRectMake(x, y, _cellWidth, _numberCellHeight);
}
/// 滚动次数
- (NSArray<NSNumber *> *)getRepeatTimesWithChangeNumber:(NSInteger)change targetNumber:(NSInteger)targetNumber {
NSMutableArray *repeatTimesArray = [[NSMutableArray alloc] init];
NSInteger originNumber = targetNumber - change;
if (change > 0) {
do {
targetNumber = (targetNumber / 10) * 10;
originNumber = (originNumber / 10) * 10;
NSNumber *repeat = @((targetNumber - originNumber) / 10);
[repeatTimesArray addObject: repeat];
targetNumber = targetNumber / 10;
originNumber = originNumber / 10;
} while ((targetNumber - originNumber) != 0);
} else {
do {
targetNumber = (targetNumber / 10) * 10;
originNumber = (originNumber / 10) * 10;
NSNumber *repeat = @((originNumber - targetNumber) / 10);
[repeatTimesArray addObject: repeat];
targetNumber = targetNumber / 10;
originNumber = originNumber / 10;
} while ((originNumber - targetNumber) != 0);
}
return repeatTimesArray;
}
/// 显示数组
- (NSArray<NSNumber *> *)getEachCellValueArrayWithTargetNumber:(NSInteger)targetNumber {
NSMutableArray *cellValueArray = [[NSMutableArray alloc] init];
NSInteger tmp;
for (NSInteger i = 0; i < _rowNumber; i++) {
tmp = targetNumber % 10;
NSNumber *number = @(tmp);
[cellValueArray addObject:number];
targetNumber = targetNumber / 10;
}
return cellValueArray;
}
/// 获取显示值
- (NSInteger)getValueOfCell:(UILabel *)cell {
CGFloat y = cell.frame.origin.y;
CGFloat tmpNumber = (- (y * numberCellLineCount / _numberCellHeight)) - 10;
NSInteger displayNumber = (NSInteger)roundf(tmpNumber);
displayNumber = abs((int)displayNumber);
return displayNumber;
}
/// 获取滚动时间
- (CGFloat)getIntervalWithPreviousNumber:(NSInteger)previousNumber targetNumber:(NSInteger)targetNumber {
NSArray *repeatTimesArray = [self getRepeatTimesWithChangeNumber: targetNumber - previousNumber targetNumber: targetNumber];
NSUInteger count = repeatTimesArray.count;
NSInteger tmp1 = targetNumber / (NSInteger)pow(10, count - 1);
NSInteger tmp2 = previousNumber / (NSInteger)pow(10, count - 1);
NSInteger maxChangeNum = labs(tmp1 % 10 - tmp2 % 10);
return 0.2 * count * maxChangeNum;
}
/// 标签
- (UILabel *)makeNumberCell {
UILabel *cell = [[UILabel alloc] init];
cell.font = _font;
cell.numberOfLines = numberCellLineCount;
cell.textColor = _textColor;
cell.text = numberCellText;
cell.textAlignment = NSTextAlignmentCenter;
return cell;
}
- (NSMutableArray *)taskQueue {
if (!_taskQueue) {
_taskQueue = [NSMutableArray arrayWithCapacity: 1];
}
return _taskQueue;
}
@end