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

Item.h

#import <UIKit/UIKit.h>
#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) NSInteger  index;

@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:_itemImageView  FromValue:fromValue  ToValue:toValue];
    [UIView animateWithDuration:0.35 animations:^{
        
        if (toValue == 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 = [NSData dataWithContentsOfFile:path];
    return  imageData;
}






  • 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 *)manager  IsSelected:(BOOL)isSelected ImageUrlArr:(NSArray *)imageUrlArr CacheBlock:(CacheIamgeBlock)cacheBlock ServerBlock:(ServerImageBlock)serverBlock  Group:(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 = [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新年快乐,猪事顺利!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容