-
前言
PM动动嘴,开发跑断腿
PM看了N个产品后觉得点击Tabbar图片能动起来是个很好的体验,本着别人有的我们也得有,能抄一套是一套的原则,我在PM的威逼利诱下开始了探索之路。
首先说为什么要用SDWebImage而不是本地去直接加载GIF或者去写帧动画,因为PM的需求是Tabbar的icon在一些重要的活动或者节假日必须要后台去返回,日常就用本地默认的icon,所以考虑用SDWebImage+FLAnimatedImage。
为什么要在GIF动画执行完一次就停止,只因为动画一直循环播放不是PM想要的效果而已。
完工后效果:
导出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新年快乐,猪事顺利!