點(diǎn)此下載源碼下載:源碼(會(huì)持續(xù)更新牲证,歡迎star军洼。保證炫酷,童叟無(wú)欺I醯)
數(shù)字動(dòng)態(tài)變化的動(dòng)畫(huà)效果
本篇文章要實(shí)現(xiàn)的動(dòng)畫(huà)效果如下大诸。
由于生成gif幀動(dòng)畫(huà)時(shí)間較短的問(wèn)題,有部分動(dòng)畫(huà)效果并沒(méi)有顯示體現(xiàn)出來(lái)贯卦。小編解析一下上面的gif動(dòng)畫(huà)效果资柔。
本篇文章最后有最終實(shí)現(xiàn)的效果圖檬姥。
解析動(dòng)畫(huà)
從上一個(gè)viewController進(jìn)入到StatsViewController源织,是一個(gè)動(dòng)畫(huà)轉(zhuǎn)場(chǎng)柳沙。(本篇文章不詳細(xì)講解)
顯示當(dāng)前視圖威始,視圖是從底部滑入型诚。在滑入進(jìn)入的過(guò)程中齐遵,視圖上的部分子視圖動(dòng)畫(huà)的變化(如:UILabel上的數(shù)字娩嚼,CAGradientLayer的顏色等)爹橱。
當(dāng)前視圖加載完畢庶灿,滾動(dòng)視圖纵搁。在滾動(dòng)過(guò)程中,有的視圖同時(shí)向兩側(cè)移動(dòng)逐漸消失或出現(xiàn)往踢,有的視圖向左逐漸移動(dòng)消失或出現(xiàn)腾誉,而有的視圖向右移動(dòng)逐漸消失或出現(xiàn)。
子視圖向左或者向右移動(dòng)的過(guò)程中菲语,部分子視圖動(dòng)態(tài)的變化妄辩。
設(shè)計(jì)思路
針對(duì)上面的每一步進(jìn)行逐步設(shè)計(jì)實(shí)現(xiàn)。
- 參見(jiàn)源碼山上,不詳細(xì)介紹眼耀。
- StatsViewController上是由一個(gè)UIScrollView,從底部滑入的動(dòng)畫(huà)佩憾。
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) {
}];
如何實(shí)現(xiàn)UILabel數(shù)字的動(dòng)態(tài)變化呢哮伟?
開(kāi)源項(xiàng)目pop動(dòng)畫(huà)幫我們實(shí)現(xiàn)了干花,小編來(lái)講解如何實(shí)現(xiàn)吧。封裝實(shí)現(xiàn)動(dòng)畫(huà)的UILabel的方法楞黄。
#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;
//是否帶有小數(shù)點(diǎn)
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 < number; i++) {
UIButton *button = [self.firstRainDropIcons objectAtIndex:i];
button.enabled = fromValue > toValue ? NO : YES;
}
}
else if ([key isEqualToString:@"second"]){
int number = (int)roundf(values[0]);
for (int i = 0; i < number; i++) {
UIButton *button = [self.secondRainDropIcons objectAtIndex:i];
button.enabled = fromValue > 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]; //動(dòng)畫(huà)屬性
anBasic.property = prop; //自定義屬性
anBasic.fromValue = @(fromValue); //從0開(kāi)始
anBasic.toValue = @(toValue); //
anBasic.duration = 1.5; //持續(xù)時(shí)間
anBasic.beginTime = CACurrentMediaTime() + 0.1; //延遲0.1秒開(kāi)始
[label pop_addAnimation:anBasic forKey:key];
}
創(chuàng)建UILabel加入到當(dāng)前視圖中池凄,實(shí)現(xiàn)數(shù)值由0到238874變化。
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上的子視圖鬼廓。(詳細(xì)講解這一部分的思路)
小編的設(shè)計(jì)思路是:每一行是一個(gè)ZFSliderAnimationView肿仑。ZFSliderAnimationView中,有一個(gè)或多個(gè)ZFSliderAnimationItem組成碎税。
ZFSliderAnimationItem的定義尤慰,如下:
@interface ZFSliderAnimationItem : NSObject
@property (nonatomic,strong) NSString *headerTitle; //標(biāo)題
@property (nonatomic,strong) NSString *content; //中間內(nèi)容
@property (nonatomic,strong) NSString *footerTitle;//底部標(biāo)題
@property (nonatomic,strong) NSString *detailContent;//詳細(xì)內(nèi)容
@property (nonatomic,strong) UIColor *itemBackgroundColor;//背景顏色
@property (nonatomic,assign) BOOL showDetail;//是否顯示詳細(xì)按鈕
@property (nonatomic,assign) BOOL showAnimated;//是否動(dòng)畫(huà)
@end
創(chuàng)建ZFSliderAnimationView可以定義多個(gè)類(lèi)型如下:
typedef NS_ENUM(NSInteger, ZFSliderStyle) {
//一般類(lèi)型 包括headTitle content footer detail
ZFSliderStyleNormal,
//可自定義單個(gè)視圖
ZFSliderStyleView,
//多個(gè)normal組成
ZFSliderStyleMultiple,
//多個(gè)customView組成
ZFSliderStyleMultipleView,
};
對(duì)應(yīng)復(fù)雜的自定義視圖,作為單獨(dú)一個(gè)View傳人到ZFSliderAnimationView中雷蹂。在本篇文章中伟端,ZFRainDropView和ZFGradientView都是自定義的視圖傳人到ZFSliderAnimationView中。這樣的好處避免ZFSliderAnimationView類(lèi)代碼顯得特別的臃腫匪煌,而且耦合性降低责蝠。只需要?jiǎng)?chuàng)建自己所期望的UIView傳人即可。
ZFSliderAnimationView根據(jù)style初始化:
-(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;
}
}
在滾動(dòng)UIScrollView時(shí)萎庭,每個(gè)item是向左還是向右移動(dòng)霜医。ZFSliderAnimationView的動(dòng)畫(huà)類(lèi)型定義如下:
typedef NS_ENUM(NSInteger, ZFSliderItemAnimation) {
ZFSliderItemAnimationLeft,//向左移動(dòng)
ZFSliderItemAnimationRight,//向右移動(dòng)
ZFSliderItemAnimationBoth//分別向兩側(cè)移動(dòng)
};
@property (nonatomic,assign) ZFSliderItemAnimation animation;
然后根據(jù)ZFSliderItemAnimation的值實(shí)現(xiàn)每個(gè)item移動(dòng):
-(void)updateAnimationView:(CGFloat)percent animated:(BOOL)animated{
if (self.animation == ZFSliderItemAnimationLeft) {
//動(dòng)態(tài)變化子視圖內(nèi)容 第4步
[self showItemAnimation:percent animated:animated];
if (percent < 0) {
percent = 0;
}
if (percent >1) {
percent =1;
}
percent = percent * 0.5;
UIView *contentView = self.subviews[0];
CGRect frame = contentView.frame;
//向左移動(dòng)
frame.origin.x = (gapWidth - contentView.frame.size.width * percent);
contentView.frame = frame;
}
else if (self.animation == ZFSliderItemAnimationRight){
//動(dòng)態(tài)變化子視圖內(nèi)容 第4步
[self showItemAnimation:percent animated:animated];
if (percent < 0) {
percent = 0;
}
if (percent >1) {
percent =1;
}
percent = percent * 0.5;
UIView *contentView = self.subviews[0];
CGRect frame = contentView.frame;
//向右移動(dòng)
frame.origin.x = (gapWidth + contentView.frame.size.width * percent);
contentView.frame = frame;
}
else if (self.animation == ZFSliderItemAnimationBoth){
//動(dòng)態(tài)變化子視圖內(nèi)容 第4步
[self showItemAnimation:percent animated:animated];
NSAssert(self.items.count > 1, @"Count can't less than two");
//此處只演示2個(gè)items
if (percent < 0) {
percent = 0;
}
if (percent >1) {
percent =1;
}
percent = percent * 0.5;
CGRect leftFrame = self.subviews[0].frame;
CGRect rightFrame = self.subviews[1].frame;
//左側(cè)item 向左移動(dòng)
leftFrame.origin.x = (gapWidth - self.subviews[0].frame.size.width * percent);
self.subviews[0].frame = leftFrame;
//右側(cè)item 向右移動(dòng)
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中,滾動(dòng)UIScrollView由UIScrollView的頂部和底部的偏移量確定當(dāng)前哪個(gè)視圖移動(dòng)的百分比擎椰。
#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;
}
//頂部視圖的移動(dòng)百分比
if (bContainedTopView) {
CGFloat percent = (scrollView.contentOffset.y - subView.frame.origin.y - middleView.frame.origin.y) /middleView.frame.size.height;
//更新動(dòng)畫(huà)視圖
[sliderView updateAnimationView:percent animated:YES];
continue;
}
//底部視圖的移動(dòng)百分比
else if (bContainedBottomView){
CGFloat percent = (point.y - subView.frame.origin.y - middleView.frame.origin.y) /middleView.frame.size.height;
//更新動(dòng)畫(huà)視圖
[sliderView updateAnimationView:1- percent animated:YES];
continue;
}
else{
//不更新動(dòng)畫(huà)視圖
[sliderView updateAnimationView:0.0 animated:NO];
}
}
}
4.動(dòng)態(tài)變化子視圖內(nèi)容支子。
-(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 實(shí)現(xiàn)動(dòng)畫(huà)
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 實(shí)現(xiàn)動(dòng)畫(huà)
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]]) {
//無(wú)法用key 區(qū)分创肥,根據(jù)子視圖上的UILabel實(shí)現(xiàn)
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 實(shí)現(xiàn)動(dòng)畫(huà)
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;
//無(wú)法用key 區(qū)分达舒,根據(jù)子視圖上的UILabel實(shí)現(xiàn)
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 實(shí)現(xiàn)動(dòng)畫(huà)
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]]) {
//無(wú)法用key 區(qū)分,根據(jù)子視圖上的UILabel實(shí)現(xiàn)
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];
}
}
}
}
}
}
最終效果圖:
結(jié)束語(yǔ)
在本篇演示的內(nèi)容中叹侄,還一部分是關(guān)于漸變顏色的取值巩搏。等分插值取值,動(dòng)態(tài)實(shí)現(xiàn)顏色的變化趾代。詳細(xì)的參考源碼中的ZFGradientView贯底。
擴(kuò)展閱讀: