點此下載原始碼下載:原始碼(會持續更新,歡迎star。保證炫酷,童叟無欺!)
數字動態變化的動畫效果
本篇文章要實現的動畫效果如下。
由於生成gif幀動畫時間較短的問題,有部分動畫效果並沒有顯示體現出來。小編解析一下上面的gif動畫效果。
本篇文章最後有最終實現的效果圖。
解析動畫
- 從上一個viewController進入到StatsViewController,是一個動畫轉場。(本篇文章不詳細講解)
- 顯示當前檢視,檢視是從底部滑入。在滑入進入的過程中,檢視上的部分子檢視動畫的變化(如:UILabel上的數字,CAGradientLayer的顏色等)。
- 當前檢視載入完畢,滾動檢視。在滾動過程中,有的檢視同時向兩側移動逐漸消失或出現,有的檢視向左逐漸移動消失或出現,而有的檢視向右移動逐漸消失或出現。
- 子檢視向左或者向右移動的過程中,部分子檢視動態的變化。
設計思路
針對上面的每一步進行逐步設計實現。
- 參見原始碼,不詳細介紹。
- StatsViewController上是由一個UIScrollView,從底部滑入的動畫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
CGRect offsetFrame = self.view.frame; offsetFrame.origin.y = self.view.frame.size.height; CGRect frame = self.view.frame; UIScrollView *scrollView = [UIScrollView new]; frame.origin.y = CGRectGetMaxY(self.navigationBarView.frame) + 10; scrollView.delegate = self; [self.view addSubview:scrollView]; scrollView.frame = offsetFrame; [UIView animateWithDuration:1.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ scrollView.frame = frame; } completion:^(BOOL finished) { }]; |
如何實現UILabel數字的動態變化呢?
開源專案pop動畫幫我們實現了,小編來講解如何實現吧。封裝實現動畫的UILabel的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#pragma mark - animationLabel method -(void)animatedForLabel:(UILabel *)label forKey:(NSString *)key fromValue:(CGFloat)fromValue toValue:(CGFloat) toValue decimal:(BOOL)decimal{ POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:key initializer:^(POPMutableAnimatableProperty *prop) { prop.readBlock = ^(id obj, CGFloat values[]) { }; prop.writeBlock = ^(id obj, const CGFloat values[]) { NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; formatter.numberStyle = NSNumberFormatterDecimalStyle; NSString *string = nil; //是否帶有小數點 if (decimal) { string = [NSString stringWithFormat:@"%.1f",values[0]]; } else{ string = [formatter stringFromNumber:[NSNumber numberWithInt:(int)values[0]]]; } if ([key isEqualToString:@"first"]) { int number = (int)roundf(values[0]); for (int i = 0; i toValue ? NO : YES; } } else if ([key isEqualToString:@"second"]){ int number = (int)roundf(values[0]); for (int i = 0; i toValue ? NO : YES; } } if (fromValue > toValue) { label.alpha = 0.5; } else{ label.alpha = 1.0; } label.text = string; }; // prop.threshold = 0.1; }]; POPBasicAnimation *anBasic = [POPBasicAnimation easeInEaseOutAnimation]; //動畫屬性 anBasic.property = prop; //自定義屬性 anBasic.fromValue = @(fromValue); //從0開始 anBasic.toValue = @(toValue); // anBasic.duration = 1.5; //持續時間 anBasic.beginTime = CACurrentMediaTime() + 0.1; //延遲0.1秒開始 [label pop_addAnimation:anBasic forKey:key]; } |
建立UILabel加入到當前檢視中,實現數值由0到238874變化。
1 2 3 4 5 6 7 8 9 |
UILabel *animationLabel = [UILabel new]; animationLabel.text = @"0"; animationLabel.textAlignment = NSTextAlignmentCenter; animationLabel.font = [UIFont fontWithName:TitleFontName size:65.0]; animationLabel.textColor = kTextlightGrayColor; animationLabel.frame = CGRectMake(0, 200, 300, 90); [self.view addSubview:animationLabel]; [self animatedForLabel:animationLabel forKey:@"animation" fromValue:0 toValue: 238874 decimal:NO]; |
3.自定義封裝UIScrollView上的子檢視。(詳細講解這一部分的思路)
小編的設計思路是:每一行是一個ZFSliderAnimationView。ZFSliderAnimationView中,有一個或多個ZFSliderAnimationItem組成。
ZFSliderAnimationItem的定義,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@interface ZFSliderAnimationItem : NSObject @property (nonatomic,strong) NSString *headerTitle; //標題 @property (nonatomic,strong) NSString *content; //中間內容 @property (nonatomic,strong) NSString *footerTitle;//底部標題 @property (nonatomic,strong) NSString *detailContent;//詳細內容 @property (nonatomic,strong) UIColor *itemBackgroundColor;//背景顏色 @property (nonatomic,assign) BOOL showDetail;//是否顯示詳細按鈕 @property (nonatomic,assign) BOOL showAnimated;//是否動畫 @end |
建立ZFSliderAnimationView可以定義多個型別如下:
1 2 3 4 5 6 7 8 9 10 11 |
typedef NS_ENUM(NSInteger, ZFSliderStyle) { //一般型別 包括headTitle content footer detail ZFSliderStyleNormal, //可自定義單個檢視 ZFSliderStyleView, //多個normal組成 ZFSliderStyleMultiple, //多個customView組成 ZFSliderStyleMultipleView, }; |
對應複雜的自定義檢視,作為單獨一個View傳人到ZFSliderAnimationView中。在本篇文章中,ZFRainDropView和ZFGradientView都是自定義的檢視傳人到ZFSliderAnimationView中。這樣的好處避免ZFSliderAnimationView類程式碼顯得特別的臃腫,而且耦合性降低。只需要建立自己所期望的UIView傳人即可。
ZFSliderAnimationView根據style初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
-(void)initSubViewsWithStyle:(ZFSliderStyle)style{ switch (style) { case ZFSliderStyleNormal: if (self.items) { [self addItemViewOnContentView:self.items[0] withCustomView:nil]; } break; case ZFSliderStyleView: if (self.customViews && self.items) { UIView *customView = [UIView new]; if (self.customViews.count > 0) { customView = self.customViews[0]; } [self addItemViewOnContentView:self.items[0] withCustomView:customView]; } break; case ZFSliderStyleMultiple: { NSMutableArray *itemWidthArray = [self countWidthForItems:self.items]; CGFloat orignX = 0; for (int i =0 ; i < self.items.count; i++) { orignX += i ? [itemWidthArray[i -1] floatValue] + gapWidth : gapWidth; [self addItemOnContentViewAtIndex:i animationItem:self.items[i] orignX:orignX withItemWidth:[itemWidthArray[i] floatValue]]; } } break; default: break; } } |
在滾動UIScrollView時,每個item是向左還是向右移動。ZFSliderAnimationView的動畫型別定義如下:
1 2 3 4 5 6 7 8 |
typedef NS_ENUM(NSInteger, ZFSliderItemAnimation) { ZFSliderItemAnimationLeft,//向左移動 ZFSliderItemAnimationRight,//向右移動 ZFSliderItemAnimationBoth//分別向兩側移動 }; @property (nonatomic,assign) ZFSliderItemAnimation animation; |
然後根據ZFSliderItemAnimation的值實現每個item移動:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
-(void)updateAnimationView:(CGFloat)percent animated:(BOOL)animated{ if (self.animation == ZFSliderItemAnimationLeft) { //動態變化子檢視內容 第4步 [self showItemAnimation:percent animated:animated]; if (percent 1) { percent =1; } percent = percent * 0.5; UIView *contentView = self.subviews[0]; CGRect frame = contentView.frame; //向左移動 frame.origin.x = (gapWidth - contentView.frame.size.width * percent); contentView.frame = frame; } else if (self.animation == ZFSliderItemAnimationRight){ //動態變化子檢視內容 第4步 [self showItemAnimation:percent animated:animated]; if (percent 1) { percent =1; } percent = percent * 0.5; UIView *contentView = self.subviews[0]; CGRect frame = contentView.frame; //向右移動 frame.origin.x = (gapWidth + contentView.frame.size.width * percent); contentView.frame = frame; } else if (self.animation == ZFSliderItemAnimationBoth){ //動態變化子檢視內容 第4步 [self showItemAnimation:percent animated:animated]; NSAssert(self.items.count > 1, @"Count can't less than two"); //此處只演示2個items if (percent 1) { percent =1; } percent = percent * 0.5; CGRect leftFrame = self.subviews[0].frame; CGRect rightFrame = self.subviews[1].frame; //左側item 向左移動 leftFrame.origin.x = (gapWidth - self.subviews[0].frame.size.width * percent); self.subviews[0].frame = leftFrame; //右側item 向右移動 rightFrame.origin.x = (self.subviews[0].frame.size.width + gapWidth *2 + self.subviews[1].frame.size.width * percent); self.subviews[1].frame = rightFrame; } //改變透明度 self.alpha = 1 - percent *2; } |
在StatsViewController中,滾動UIScrollView由UIScrollView的頂部和底部的偏移量確定當前哪個檢視移動的百分比。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#pragma mark - UIScrollViewDelegate -(void)scrollViewDidScroll:(UIScrollView *)scrollView{ for (UIView *subView in _subViewsArray) { BOOL bContainedTopView = CGRectContainsPoint(subView.frame,scrollView.contentOffset); CGPoint point = scrollView.contentOffset; point.y += (self.view.frame.size.height - CGRectGetMaxY(self.navigationBarView.frame) -10); BOOL bContainedBottomView = CGRectContainsPoint(subView.frame,point); ZFSliderAnimationView *sliderView = (ZFSliderAnimationView *)subView; UIView *middleView = nil; //自定義的view if (sliderView.style == ZFSliderStyleView || sliderView.style == ZFSliderStyleMultipleView) { middleView = sliderView.customView; } else{ middleView = sliderView.contentLabel; } //頂部檢視的移動百分比 if (bContainedTopView) { CGFloat percent = (scrollView.contentOffset.y - subView.frame.origin.y - middleView.frame.origin.y) /middleView.frame.size.height; //更新動畫檢視 [sliderView updateAnimationView:percent animated:YES]; continue; } //底部檢視的移動百分比 else if (bContainedBottomView){ CGFloat percent = (point.y - subView.frame.origin.y - middleView.frame.origin.y) /middleView.frame.size.height; //更新動畫檢視 [sliderView updateAnimationView:1- percent animated:YES]; continue; } else{ //不更新動畫檢視 [sliderView updateAnimationView:0.0 animated:NO]; } } } |
4.動態變化子檢視內容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
-(void)showItemAnimation:(CGFloat)percent animated:(BOOL)animated{ if (percent > 0 && percent < 1) { for (int i = 0; i < self.subItemViews.count; i++) { NSArray *subViews = ((UIView *)self.subItemViews[i]).subviews; //ZFRainDropView 實現動畫 if ([[subViews[1] class] isSubclassOfClass:[ZFRainDropView class]]) { ZFRainDropView *rainDropView = subViews[1]; if (!rainDropView.digitAnimated) { return; } [rainDropView increaseNumber:NO animated:animated]; rainDropView.digitAnimated = NO; return; } //ZFGradientView 實現動畫 else if([[subViews[1] class] isSubclassOfClass:[ZFGradientView class]]){ ZFGradientView *gradientView = subViews[1]; if (!gradientView.digitAnimated) { return; } [gradientView increaseNumber:NO animated:animated]; gradientView.digitAnimated = NO; return; } for (UIView *subView in subViews) { if ([subView isKindOfClass:[UILabel class]]) { //無法用key 區分,根據子檢視上的UILabel實現 ZFSliderAnimationItem *item = self.items[i]; if (!item.showAnimated) { return; } if ([item.content rangeOfString:@"."].length > 0) { [self animatedForLabel:(UILabel *)subView forKey:self.animationKey fromValue:[item.content floatValue] toValue:0 decimal:YES]; } else{ [self animatedForLabel:(UILabel *)subView forKey:self.animationKey fromValue:[item.content floatValue] toValue:0 decimal:NO]; } item.showAnimated = NO; } } } } else if(percent < 0){ for (int i = 0; i < self.subItemViews.count; i++) { NSArray *subViews = ((UIView *)self.subItemViews[i]).subviews; if ([[subViews[1] class] isSubclassOfClass:[ZFRainDropView class]]) { //ZFRainDropView 實現動畫 ZFRainDropView *rainDropView = subViews[1]; if (!rainDropView.digitAnimated) { [rainDropView increaseNumber:YES animated:animated]; rainDropView.digitAnimated = YES; } return; } else if([[subViews[1] class] isSubclassOfClass:[ZFGradientView class]]){ ZFGradientView *gradientView = subViews[1]; if (!gradientView.digitAnimated) { [gradientView increaseNumber:YES animated:animated]; gradientView.digitAnimated = YES; } return; } for (UIView *subView in subViews) { if ([subView isKindOfClass:[UILabel class]]) { UILabel *contentLabel = (UILabel *)subView; //無法用key 區分,根據子檢視上的UILabel實現 ZFSliderAnimationItem *item = self.items[i]; if (!item.showAnimated) { if ([item.content rangeOfString:@"."].length > 0) { [self animatedForLabel:contentLabel forKey:self.animationKey fromValue:0 toValue:[item.content floatValue] decimal:YES]; } else{ [self animatedForLabel:contentLabel forKey:self.animationKey fromValue:0 toValue:[item.content floatValue] decimal:NO]; } item.showAnimated = YES; } } } } } else if(percent == 0){ if (!animated) { return; } for (int i = 0; i < self.subItemViews.count; i++) { NSArray *subViews = ((UIView *)self.subItemViews[i]).subviews; if ([[subViews[1] class] isSubclassOfClass:[ZFRainDropView class]]) { //ZFRainDropView 實現動畫 ZFRainDropView *rainDropView = subViews[1]; [rainDropView increaseNumber:NO animated:animated]; return; } else if([[subViews[1] class] isSubclassOfClass:[ZFGradientView class]]){ ZFGradientView *gradientView = subViews[1]; [gradientView increaseNumber:NO animated:animated]; return; } for (UIView *subView in subViews) { if ([subView isKindOfClass:[UILabel class]]) { //無法用key 區分,根據子檢視上的UILabel實現 ZFSliderAnimationItem *item = self.items[i]; if ([item.content rangeOfString:@"."].length > 0) { [self animatedForLabel:(UILabel *)subView forKey:self.animationKey fromValue:0 toValue:[item.content floatValue] decimal:YES]; } else{ [self animatedForLabel:(UILabel *)subView forKey:self.animationKey fromValue:0 toValue:[item.content floatValue] decimal:NO]; } } } } } } |
最終效果圖:
結束語
在本篇演示的內容中,還一部分是關於漸變顏色的取值。等分插值取值,動態實現顏色的變化。詳細的參考原始碼中的ZFGradientView。
擴充套件閱讀: