SDWebImage+FLAnimatedImage,给Tabbar添加动图







  • 前言
PM动动嘴,开发跑断腿
PM看了N个产品后觉得点击Tabbar图片能动起来是个很好的体验,本着别人有的我们也得有,能抄一套是一套的原则,我在PM的威逼利诱下开始了探索之路。
首先说为什么要用SDWebImage而不是本地去直接加载GIF或者去写帧动画,因为PM的需求是Tabbar的icon在一些重要的活动或者节假日必须要后台去返回,日常就用本地默认的icon,所以考虑用SDWebImage+FLAnimatedImage。
为什么要在GIF动画执行完一次就停止,只因为动画一直循环播放不是PM想要的效果而已。
完工后效果
tababr_gif.gif
导出GIF有卡段,视频链接:https://player.youku.com/embed/XNDAxNDIxMjY4NA==




  • 参考
一位大牛的实现动画方式深度解析:https://www.jianshu.com/p/8e0f560188ff
SDWebImage:https://github.com/SDWebImage/SDWebImage
FLAnimatedImage:https://github.com/Flipboard/FLAnimatedImage




  • FLAnimatedImage
1. FLAnimatedImage具体解读请阅读:https://www.jianshu.com/p/10644979f01c

2. FLAnimatedImage的主要API和属性
/ **GIF封面图片 ** / @property (nonatomic, strong, readonly) UIImage *posterImage; / **GIF封面图片尺寸 ** / @property (nonatomic, assign, readonly) CGSize size; / **GIF循环播放次数 ** / @property (nonatomic, assign, readonly) NSUInteger loopCount; / **每帧图片的显示时间集合 ** / @property (nonatomic, strong, readonly) NSDictionary *delayTimesForIndexes; / **动画的帧数量 ** / @property (nonatomic, assign, readonly) NSUInteger frameCount; / **当前被缓存的帧图片的总数量 ** / @property (nonatomic, assign, readonly) NSUInteger frameCacheSizeCurrent; // / **允许缓存多少帧图片 ** / @property (nonatomic, assign) NSUInteger frameCacheSizeMax; // 允/ **从缓存中获取传递下标的图片 ** / - (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index; / **初始化 FLAnimatedImage ** / - (instancetype)initWithAnimatedGIFData:(NSData *)data; - (instancetype)initWithAnimatedGIFData:(NSData *)data optimalFrameCacheSize:(NSUInteger)optimalFrameCacheSize predrawingEnabled:(BOOL)isPredrawingEnabled NS_DESIGNATED_INITIALIZER; + (instancetype)animatedImageWithGIFData:(NSData *)data;



3. FLAnimatedImageView的主要API和属性
/ **GIF动画播放一次之后的回调Block ** / @property (nonatomic, copy) void(^loopCompletionBlock)(NSUInteger loopCountRemaining); / **GIF动画当前显示的帧图片 ** / @property (nonatomic, strong, readonly) UIImage *currentFrame; /** GIF动画当前显示的帧图片索引 **/ @property (nonatomic, assign, readonly) NSUInteger currentFrameIndex;



4. FLAnimatedImage使用 在我的项目中TabbarItem是自定义的View,View上添加FLAnimatedImageView
【SDWebImage+FLAnimatedImage,给Tabbar添加动图】Item.h
#import #import "WKTabbarModel.h" #import "FLAnimatedImageView+WebCache.h" #import "FLAnimatedImage.h"@interface WKTabbarItem :UIView@property (strong, nonatomic) UILabel *itemLabel; @property (strong, nonatomic) FLAnimatedImageView *itemImageView; /** 是否选中 */ @property (assign, nonatomic) BOOL isSelected; /** tababr item数据 @param model model @param index index */ - (void)setModel:(WKTabbarModel *)model Index:(NSInteger)index; /** 点击tabbar item放大 @param fromValue 动画的起始状态值 @param toValue 动画的结束状态值 @param height height */ - (void)changeShowScaleAnimationFromValue:(CGFloat)fromValue ToValue:(CGFloat)toValue Height:(CGFloat)height;

Item.m
#import "WKTabbarItem.h" #import "CAAnimation+WKAnimation.h"@interface WKTabbarItem() { WKTabbarModel *_model; }@property (assign, nonatomic) NSIntegerindex; @end@implementation WKTabbarItem- (instancetype)initWithFrame:(CGRect)frame{if (self = [super initWithFrame:frame]) {[self setLayoutView]; } return self; }- (void)setLayoutView{_itemImageView = [FLAnimatedImageView new]; _itemImageView.contentMode = UIViewContentModeScaleAspectFit; [self addSubview:_itemImageView]; _itemImageView.sd_layout .topSpaceToView(self , (IS_IPHONE_10)?(10):(5)) .widthRatioToView(self , 1) .centerXEqualToView(self ) .heightIs((IS_IPHONE_10)?(28):(26)); }//tababr model - (void)setModel:(WKTabbarModel *)model Index:(NSInteger)index{_model = model; _index = index; [self initItemData]; }//tababr 图片文字赋值 - (void)initItemData{_itemLabel.text = _model.title; if (self.isSelected) {_itemLabel.textColor = WKFontOrangeColor; [self setSelectedImage]; }else{_itemLabel.textColor = WKLightGrayColor; [self setNormalImage]; } }//动画改变item - (void)changeShowScaleAnimationFromValue:(CGFloat)fromValue ToValue:(CGFloat)toValue Height:(CGFloat)height{[self initItemData]; [WKTabbarStyleManager showScaleAnimationInView:_itemImageViewFromValue:fromValueToValue:toValue]; [UIView animateWithDuration:0.35 animations:^{if (toValue =https://www.it610.com/article/= 1) {self.mj_y = 0; self.mj_h = height; }else{ self.mj_y = -20; self.mj_h = height + 20; } }]; }//设置选中图片 - (void)setSelectedImage{NSString *fileName = [_model.selectedImageUrl pathExtension]; if([fileName isEqualToString:@"gif"]){//gif用FLAnimatedImage加载NSArray *selImageArr = [WKUserDefaultsTool loadTabbarDownloaderSelectedImages]; NSArray *norIamgeArr = [WKUserDefaultsTool loadTabbarDownloaderNormalImages]; if (selImageArr.count >= 4 && norIamgeArr.count >= 4) { //下载保存成功后走本地保存的imageData,这样写是为了避免每次启动刷新NSData *imageData = selImageArr[_index]; [self setAnimatedImageWithData:imageData]; }else{ //第一次加载新的图片,走sd缓存[self setAnimatedImageWithData:[self setImageDataWithUrl:_model.selectedImageUrl]]; } }else{ _itemImageView.image = (![self setImageDataWithUrl:_model.selectedImageUrl])?([UIImage imageNamed:_model.selectedPlaceImage]):([UIImage imageWithData:[self setImageDataWithUrl:_model.selectedImageUrl]]); } }//设置未选中图片 - (void)setNormalImage{NSArray *selImageArr = [WKUserDefaultsTool loadTabbarDownloaderSelectedImages]; NSArray *norIamgeArr = [WKUserDefaultsTool loadTabbarDownloaderNormalImages]; if (selImageArr.count >= 4 && norIamgeArr.count >= 4) { //下载保存成功后走本地保存的imageData,这样写是为了避免每次启动刷新_itemImageView.image = [UIImage imageWithData:norIamgeArr[_index]]; }else{ //第一次加载新的图片,走sd缓存 [_itemImageView sd_setImageWithURL:[NSURL URLWithString:_model.normalImageUrl] placeholderImage:[UIImage imageNamed:_model.normalPlaceImage]]; } }/*加载选中gif动态图片 如果是本地的gif,data可是直接写成本地路径的image转data 我的项目中是sdwebimage的缓存*/ - (void)setAnimatedImageWithData:(NSData *)imageData{if (imageData) {WeakSelf(weakSelf); FLAnimatedImage *gifImage= [FLAnimatedImage animatedImageWithGIFData:imageData]; _itemImageView.animatedImage = gifImage; //gif播放完毕停止到倒数第二张 _itemImageView.loopCompletionBlock = ^(NSUInteger loopCountRemaining) { weakSelf.itemImageView.image = [gifImage imageLazilyCachedAtIndex:gifImage.frameCount - 2]; }; }}//获取图片地址在sdwebimage缓存路径 - (NSData *)setImageDataWithUrl:(NSString *)url{NSString *path = [[[SDWebImageManager sharedManager] imageCache] defaultCachePathForKey:url]; NSData*imageData = https://www.it610.com/article/[NSData dataWithContentsOfFile:path]; returnimageData; }







  • APP下载后台返回的Tabbar图片
#pragma mark ==================== tabbar样式 ==================== + (void)requestTabbarStyle{dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{AppDelegate *aDelegate= (AppDelegate *)WKApplication; NSString *url= [NSString stringWithFormat:@"%@/%@",apiAddressDEF,bottomButtonsDEF]; NSString *encryStr= [NSString stringWithFormat:@"version=%@",APPVERSION]; NSDictionary *parmsEncry = [WKTool DictionaryFromString:encryStr]; [WKRequest requestPostWithPath:url Parameters:parmsEncry CallBack:^(id obj) {//obj[@"status"] = @"0"; if ([obj[@"status"]integerValue] == 1) {NSDictionary *dataDic = [WKTool dictionaryWithJsonString:[Encryption decrypt:obj[@"data"]]]; NSArray *dataList= dataDic[@"dataList"]; NSMutableArray *normolImgUrlArr= [NSMutableArray array]; //未选中 NSMutableArray *selectedImgUrlArr = [NSMutableArray array]; //选中 NSMutableArray *titleArr= [NSMutableArray array]; //文字// 选中 static NSArray *selCacheArr; //获取sd缓存路径图片数组 static NSArray *selServerArr; //获取sd第一次缓存图片数组 //非选中 static NSArray *norCacheArr; //获取sd缓存路径图片数组 static NSArray *norServerArr; //获取sd第一次缓存图片数组if (dataDic.allKeys > 0) {for (NSDictionary *dic in dataList) {[normolImgUrlArr addObject:dic[@"img1"]]; [selectedImgUrlArr addObject:dic[@"img2"]]; [titleArr addObject:dic[@"text"]]; }//后台更新图片,删除之前的缓存 if (![selectedImgUrlArr isEqualToArray:[WKUserDefaultsTool loadTabbarSelectedImg]]) { [self clearImageCache]; }// 创建下载图片完成线程 group dispatch_group_t group = dispatch_group_create(); //下载选中tabbar图片 [self tabbarThemeWithManager:aDelegate.sdWebImgManager IsSelected:YES ImageUrlArr:selectedImgUrlArr CacheBlock:^(NSArray *cacheImgArr) {selCacheArr = cacheImgArr; } ServerBlock:^(NSArray *serverImgArr) { selServerArr = serverImgArr; } Group:group]; //下载非选中tabbar图片 [self tabbarThemeWithManager:aDelegate.sdWebImgManager IsSelected:NO ImageUrlArr:normolImgUrlArr CacheBlock:^(NSArray *cacheImgArr) {norCacheArr = cacheImgArr; } ServerBlock:^(NSArray *serverImgArr) { norServerArr = serverImgArr; } Group:group]; // !!!!!! 下载图片完成后, 回到主线再更新tababr !!!!!!! dispatch_group_notify(group, dispatch_get_main_queue(), ^{BOOL isSelImage = (selCacheArr.count >= 4 || selServerArr.count >= 4)?(YES):(NO); BOOL isNorImage = (norCacheArr.count >= 4 || norServerArr.count >= 4)?(YES):(NO); BOOL isSuccess= [[[NSUserDefaults standardUserDefaults]objectForKey:@"TabbrSuccess"] boolValue]; // 条件满足则表示全部下载完毕,避免下载的时间耗完图片可能并没有全部加载,会造成一些是本地一些是服务器图片的情况, 一次性加载刷新UI if (isSelImage && isNorImage && !isSuccess) {[self saveTabbarStyleWithTitles:titleArr NormolImages:normolImgUrlArr SelectedImages:selectedImgUrlArr ItemAnimationValue:(APPWIDTH <= 375)?(1.80):(2.0)]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"TabbrSuccess"]; }}); }else{ //清空缓存tabbar图片[self clearImageCache]; dispatch_async(dispatch_get_main_queue(), ^{ [self saveTabbarStyleWithTitles:nil NormolImages:nil SelectedImages:nil ItemAnimationValue:1.00]; }); }NSLog(@"******** %@tabbar样式 ******",dataDic); }else if([obj[@"status"]integerValue] == 0){//清空缓存tabbar图片[self clearImageCache]; // 创建下载图片完成线程 group dispatch_async(dispatch_get_main_queue(), ^{ [self saveTabbarStyleWithTitles:nil NormolImages:nil SelectedImages:nil ItemAnimationValue:1.00]; }); }}]; }); }/** 清除缓存 */ + (void)clearImageCache{[[SDImageCache sharedImageCache] clearMemory]; [[SDImageCache sharedImageCache] clearDiskOnCompletion:nil]; [WKUserDefaultsTool saveTabbarDownloaderSelectedImages:nil]; [WKUserDefaultsTool saveTabbarDownloaderNormalImages:nil]; [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"TabbrSuccess"]; }/** 保存服务器并设置tabbar样式 @param titleArr 标题 @param normolImgArr 默认 @param selectedImgArr 选中 @param tabbarAnimationValue 放大动画区间 */ + (void)saveTabbarStyleWithTitles:(NSMutableArray *)titles NormolImages:(NSMutableArray *)normolImages SelectedImages:(NSMutableArray *)selectedImages ItemAnimationValue:(CGFloat)itemAnimationValue{[WKUserDefaultsTool saveTabbarText:titles TabbarNormolImg:normolImages TabbarSelectedImg:selectedImages TabbarAnimationValue:itemAnimationValue]; AppDelegate *aDelegate= (AppDelegate *)WKApplication; WKTabBarController *tabBarC = (WKTabBarController *)aDelegate.forceViewController.tabBarController; [tabBarC.tabBarView setChangeWithSelectedIndex:WKVARIABLE.isTabbarIndex ItemTitleArr:titles ItemImageNorArr:normolImages ItemImageSelArr:selectedImages]; }/** 服务器tabbar图片 回调cacheBlock和serverBlock的目的,用户可能处于弱网环境,在下载的时间耗完图片可能并没有全部加载,会造成一些是本地一些事服务器图片的情况 所以一次分别回调两个数组,然后计算count判断是全部下完,一次性全部显示 @param manager SDWebImageManager @param isSelected 是否是下载选中图片(保存选中图片data,选中在某些情况会反gif,保存下来减少网络开销) @param imageUrlArr 下载地址 @param cacheBlock 回调已下载成功的数组 @param serverBlock 回调第一次成功的数组 @param group dispatch_group_t */ + (void)tabbarThemeWithManager:(SDWebImageManager *)managerIsSelected:(BOOL)isSelected ImageUrlArr:(NSArray *)imageUrlArr CacheBlock:(CacheIamgeBlock)cacheBlock ServerBlock:(ServerImageBlock)serverBlockGroup:(dispatch_group_t)group{NSMutableArray *cacheImgArr= [NSMutableArray array]; //本地保存过的 NSMutableArray *serverImgArr = [NSMutableArray array]; //下载成功过的for (NSString *imageUrlStr in imageUrlArr) {dispatch_group_enter(group); NSString *imagePath = imageUrlStr; NSData*imageData = https://www.it610.com/article/[self imageDataFromDiskCacheWithKey:imagePath]; if (imageData) {if (isSelected) {[cacheImgArr addObject:imageData]; [WKUserDefaultsTool saveTabbarDownloaderSelectedImages:cacheImgArr]; NSLog(@"+++++tabbar选中已保存 +++++"); }else{ [cacheImgArr addObject:imageData]; [WKUserDefaultsTool saveTabbarDownloaderNormalImages:cacheImgArr]; NSLog(@"+++++tabbar未选中已保存 +++++"); } cacheBlock(cacheImgArr); dispatch_group_leave(group); } else {NSURL *url = [NSURL URLWithString:imageUrlStr]; [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:url options:SDWebImageDownloaderHighPriority progress:nil completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {[[[SDWebImageManager sharedManager] imageCache] storeImage:image imageData:data forKey:url.absoluteString toDisk:YES completion:nil]; if (error) { // 加载失败 NSLog(@"~~~~~~~~~~~tabbar下载失败 ~~~~~~~~~~~"); } else { // 加载成功 NSLog(@"!!!!!!!!!tabbar下载完成 !!!!!!!!!"); [serverImgArr addObject:data]; serverBlock(serverImgArr); } dispatch_group_leave(group); }]; } } }







至此,PM要的功能已实现,另外我的TabbarView也是自定义的,代码就不贴了,基本上就是调用自定义item的方法。其实也是觉得现在这种线程下载实现动画的方法还是太low,先放上做个笔记,过完新年来重构。
2019新年快乐,猪事顺利!

    推荐阅读