先来看看实现的效果图
类似于猫眼电影加载等待动画的效果(盯着猫眼好好看,有惊喜!)
这是我在之前公司项目中使用的加载等待动画,现项目中使用的还是系统的UIActivityIndicatorView,等后期项目优化会让UI做些图片更换,说多了,来说说这种类似gif动画的实现方式吧.
一种是使用系统的动画类(基于CAAnimation)来实现视图的旋转效果.
另一种是使用UIImageView加载动态视图(一系列帧视图)来实现.
本文就是使用的第二种.
关于实现:无非就是在window或者当前VC中加载一个UIView,在此UIView上加载UIImageView+UILabel,设置UIImageView.animationImages 加载需要播放的帧视图数组,调用[self.imageView startAnimating];实现动画的播放!
这里我创建了两个视图,一个是底部的view(SXLoadingView),另一个是加在SXLoadingView上面的动画播放的视图(SXAnimationIndicator).
其中SXLoadingView.h
#import <UIKit/UIKit.h>
#import "SXAnimationIndicator.h"
@interface SXLoadingView : UIView
@property (nonatomic, weak) SXAnimationIndicator *animationIndicator;
+ (SXLoadingView *) loadingView;
/**
* 加载并开始动画,默认将视图SXLoadingView加载在window上
*/
+ (SXLoadingView *) loadingViewAndStartAnimation;
/**
*
* 加载并开始动画,默认将SXLoadingView视图加载在window上
*
* @param frame frame
*
* @param title 显示的标题(加载中...)
*/
+ (SXLoadingView *) loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title;
/**
*
* @param frame frame
*
* @param title 显示的标题(加载中...)
*
* @param view loadView父视图
*
*/
+ (SXLoadingView *) loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title InView:(UIView *)view;
@end
SXLoadingView.m
#import "SXLoadingView.h"
#import "UIView+SX.h"
@implementation SXLoadingView
#define ScreenSize ([UIScreen mainScreen].bounds.size)
/** 设备为iphonex */
#define DEVICE_IS_iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
/** 距屏幕上边距离 */
#define TOP_HEIGHT (DEVICE_IS_iPhoneX ? 88 : 64)
/** 距屏幕下边距离 */
#define BOTTOM_HEIGHT (DEVICE_IS_iPhoneX ? 34 : 0)
+ (SXLoadingView *) loadingView
{
return [[self alloc]init];
}
- (instancetype) init
{
if (self = [super init]) {
self.alpha = 1;
SXAnimationIndicator *indicator = [[SXAnimationIndicator alloc]initWithFrame:CGRectMake(0, (ScreenSize.height - TOP_HEIGHT - 80)/2, ScreenSize.width, 50+10+20)];
indicator.imageView.centerX = ScreenSize.width/2;
indicator.label.centerX = ScreenSize.width/2;
[self addSubview:indicator];
self.animationIndicator = indicator;
}
return self;
}
/**
* 加载并开始动画,默认将视图SXLoadingView加载在window上
*/
+ (SXLoadingView *) loadingViewAndStartAnimation
{
SXLoadingView *loadView = [SXLoadingView loadingView];
loadView.frame = CGRectMake(0, TOP_HEIGHT,ScreenSize.width, ScreenSize.height);
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
loadView.layer.zPosition = INT8_MAX;
[window addSubview:loadView];
[loadView.animationIndicator startAnimation];
return loadView;
}
/**
*
* 加载并开始动画,默认将SXLoadingView视图加载在window上
*
* @param frame frame
*
* @param title 显示的标题(加载中...)
*/
+ (SXLoadingView *) loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title
{
SXLoadingView *loadView = [SXLoadingView loadingView];
if (CGRectIsNull(frame)) {
frame = CGRectMake(0, TOP_HEIGHT,ScreenSize.width, ScreenSize.height);
}
[loadView showTitle:title];
loadView.frame = frame;
//使用keyWindow当你的app需要跳转到别的app然后返回本app的时候, 有可能会导致UI错乱
//UIWindow* window = [UIApplication sharedApplication].keyWindow;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
// 解决了loadview被VC中view覆盖没法显示的问题(查看视图层级还是被覆盖了)
loadView.layer.zPosition = INT8_MAX;
[window addSubview:loadView];
[loadView.animationIndicator startAnimation];
return loadView;
}
/**
*
* @param frame frame
*
* @param title 显示的标题(加载中...)
*
* @param view loadView父视图
*
*/
+ (SXLoadingView *) loadingViewAndStartAnimationWithFrame:(CGRect)frame title:(NSString *)title InView:(UIView *)view
{
SXLoadingView *loadView = [SXLoadingView loadingView];
[loadView showTitle:title];
if (CGRectIsNull(frame)) {
frame = CGRectMake(0, TOP_HEIGHT,ScreenSize.width, ScreenSize.height);
}
loadView.frame = frame;
[view addSubview:loadView];
[loadView.animationIndicator startAnimation];
return loadView;
}
// 此接口可开放出去
- (SXAnimationIndicator *) showTitle:(NSString *)title
{
self.animationIndicator.label.text = title;
return self.animationIndicator;
}
@end
SXAnimationIndicator.h
#import <UIKit/UIKit.h>
@interface SXAnimationIndicator : UIView
@property (nonatomic, weak) UIImageView *imageView;
@property (nonatomic, strong) UILabel *label;
@property (nonatomic, assign) BOOL isAnimating;
- (id) initWithFrame:(CGRect)frame;
- (void) startAnimation;
- (void) stopAnimation;
@end
SXAnimationIndicator.m
#import "SXAnimationIndicator.h"
@implementation SXAnimationIndicator
#define ScreenSize ([UIScreen mainScreen].bounds.size)
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_isAnimating = NO;
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
[self addSubview:imageView];
_imageView = imageView;
_imageView.animationImages = [self getImageArray];
_label = [[UILabel alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(_imageView.frame)+10, ScreenSize.width, 20)];
_label.textAlignment = NSTextAlignmentCenter;
_label.textColor = [UIColor lightGrayColor];
_label.font = [UIFont systemFontOfSize:13];
[self addSubview:_label];
self.layer.hidden = YES;
}
return self;
}
- (NSArray *) getImageArray {
NSMutableArray *imageArray = [[NSMutableArray alloc] init];
for (int i = 1; i <= 8; i++) {
NSString *imageName = [NSString stringWithFormat:@"animation%d.png", i];
// 性能优化,直接以文件路径的形式创建image,没有缓存,自动释放,imageNamed: 有缓存退出程序释放
NSString *path = [[NSBundle mainBundle] pathForResource:@"animationImage" ofType:@"bundle"];
NSString *imagePath = [NSString stringWithFormat:@"%@/%@", path, imageName];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
[imageArray addObject:image];
}
return imageArray;
}
- (void) startAnimation
{
_isAnimating = YES;
self.layer.hidden = NO;
[self doAnimation];
}
-(void) doAnimation{
//设置动画总时间
self.imageView.animationDuration = 1;
//设置重复次数,0表示无限
self.imageView.animationRepeatCount = 0;
//开始动画
[self.imageView startAnimating];
}
- (void) stopAnimation
{
_isAnimating = NO;
[self.imageView stopAnimating];
self.imageView.animationImages = nil;
}
@end
使用样例 直接在你需要展示的VC中
- (void) viewDidLoad {
[super viewDidLoad];
/* 将各参数分开
SXLoadingView* loadingView = [SXLoadingView loadingView];
[self.view addSubview:loadingView];
loadingView.frame = CGRectMake(0, TOP_HEIGHT, ScreenSize.width, ScreenSize.height);
loadingView.animationIndicator.label.text = @"加载中...";
[loadingView.animationIndicator startAnimation];
self.loadingView = loadingView;
*/
// 将参数封装在一起
self.loadingView = [SXLoadingView loadingViewAndStartAnimationWithFrame:CGRectMake(0, TOP_HEIGHT, ScreenSize.width, ScreenSize.height) title:@"加载中..." InView:self.view];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.loadingView.animationIndicator stopAnimation];
[self.loadingView removeFromSuperview];
});
}
最后来说说实现过程中遇到以及需要注意的问题.
1. 关于加载帧动画图片性能优化的处理
使用的是imageWithContentsOfFile:
来加载图片,而非imageNamed:
前者还需要将图片放入.bundle文件中,是否过于麻烦?
关于这个问题不做过多讲解了,参考下面这篇文章
http://blog.csdn.net/zyr124/article/details/50053185
2. 关于内存泄漏的处理
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 以前只是简单的将视图移除了
[self.loadingView removeFromSuperview];
});
修改后
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.loadingView.animationIndicator stopAnimation];
[self.loadingView removeFromSuperview];
});
3. window为nil
//window为nil
UIWindow *window = [UIApplication sharedApplication].keyWindow;
更改后
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
此时window是不为nil了,但是加在window上面的loadView,被VC视图遮挡(紫色部分)
于是我想到了使用 bringSubviewToFront 来实现,可是不起作用.
直到做了下面的处理之后
/*
* 改变图层的显示顺序(但不能改变的事件的传递顺序,所以视图层级还是原来那样,该覆盖的还是覆盖)给zPosition提高一个像素 (视图都非常薄,0.1也能做到)
* 和UIView严格的二维坐标系不同,CALayer存在于一个三维空间当中。
* zPosition最实用的功能就是改变图层的显示顺序了
*/
loadView.layer.zPosition = INT8_MAX;//你传递 1 也可以
运行效果正常
可是打开视图层级,发现层级跟上面的还是一样是被覆盖的(虽然运行效果正常!!!)
这是因为使用 loadView.layer.zPosition = INT8_MAX;只是改变了视图层级的“显示顺序”,事件的传递以及层级还是跟原来一样,并未改变.