最近项目里有这样一个需求:在搜索人的时候,提示一些信息,这些信息用折叠的方式把一条把上一条推掉,类似立方体翻转。具体效果可以如下图:
刚好的这个效果在之前一篇博客里看过:iOS动画-Transform和KeyFrame动画,这篇文章大家可以看下具体原理介绍,本篇只说下一些注意点。不过他那个动画只做一次,产品需要做成循环的,然后想着看能不能做成可以4个方向的,就封装了下,实现效果如下:
那要封装的话,就需要提供动画的开始,暂停,结束的接口,同时有一个初始化方法。那么大致就提炼出了以下视图头文件:
#import <UIKit/UIKit.h>
//循环方向
typedef NS_ENUM(NSInteger, CircleDirection) {
CircleDirectionDown = 0,
CircleDirectionRight,
CircleDirectionUp,
CircleDirectionLeft
} ;
@interface CPPseudoFoldCircleView : UIView
/**
* 初始化方法
*
* @param frame 视图大小,最好和内容一样大
* @param views 要循环的那些视图
* @param direction 循环方向
* @param duration 动画时间
*
* @return instancetype
*/
- (instancetype)initWithFrame:(CGRect)frame circleViews:(NSArray<UIView *> *)views direction:(CircleDirection)direction duration:(NSTimeInterval)duration;
/**
* 动画开始
*/
- (void)start;
/**
* 暂停动画
*/
- (void)pause;
/**
* 停止动画
*/
- (void)stop;
@end
相应的实现文件如下:
#import "CPPseudoFoldCircleView.h"
@interface CPPseudoFoldCircleView ()
@property (nonatomic, strong) NSTimer *timer;//定时器,用来循环动画
@property (nonatomic, assign) CircleDirection direction;//循环方向
@property (nonatomic, strong) NSArray<UIView *> *views;//要循环的视图
@property (nonatomic, assign) NSInteger curIndex;//动画结束时,当前显示的视图索引
@property (nonatomic, strong) UIView *view1;//做动画的视图一
@property (nonatomic, strong) UIView *view2;//做动画的视图二
@end
@implementation CPPseudoFoldCircleView
- (instancetype)initWithFrame:(CGRect)frame circleViews:(NSArray<UIView *> *)views direction:(CircleDirection)direction duration:(NSTimeInterval)duration {
self = [super initWithFrame:frame];
if (self) {
if (views.count < 2) {
return nil;
}
_direction = direction;
_views = views;
[self.view1 addSubview:_views[0]];
[self.view2 addSubview:_views[1]];
_timer = [NSTimer scheduledTimerWithTimeInterval:duration target:self selector:@selector(animationForCircle) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
_curIndex = 1;
}
return self;
}
#pragma mark - others
- (void)animationForCircle {
CGFloat offset;
switch (_direction) {
case CircleDirectionDown:
offset = -self.view2.frame.size.height / 2.0;//除以2 是因为CGAffineTransformMakeScale 缩放是中心缩放,配合平移就一半就到移动到视图边界了
break;
case CircleDirectionRight:
offset = -self.view2.frame.size.width / 2.0;
break;
case CircleDirectionUp:
offset = self.view2.frame.size.height / 2.0;
break;
case CircleDirectionLeft:
offset = self.view2.frame.size.width / 2.0;
break;
default:
break;
}
BOOL isHorisontal = (_direction == CircleDirectionRight) || (_direction == CircleDirectionLeft);
self.view2.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(isHorisontal ? 0 : 1, isHorisontal ? 1 : 0), CGAffineTransformMakeTranslation(isHorisontal ? offset : 0 , isHorisontal ? 1 : offset));
CGAffineTransform transform = CGAffineTransformConcat(CGAffineTransformMakeScale(isHorisontal ? 0.01 : 1, isHorisontal ? 1 : 0.01), CGAffineTransformMakeTranslation(isHorisontal ? -offset : 0, isHorisontal ? 0 : -offset ));
[UIView animateWithDuration:self.timer.timeInterval - 0.1 animations:^{//这个时间一定比timer间隔时间要短一点点,不然出现动画没做完,下一个timer的迭代又来了,出现不符合预期的效果
self.view2.alpha = 1;
self.view1.alpha = 0;
self.view2.transform = CGAffineTransformIdentity;
self.view1.transform = transform;
} completion:^(BOOL finished) {
if (finished) {
self.view1.transform = CGAffineTransformIdentity;
[self bringSubviewToFront:self.view2];
self.view1.alpha = 1;
_curIndex = (_curIndex + 1) % self.views.count;
[self swapView];//动画完后,交换view1和view2,同时更新view2的内容,以备下一个动画使用
for (UIView *temp in self.view2.subviews) {
[temp removeFromSuperview];
}
[self.view2 addSubview:_views[_curIndex]];
}
}];
}
#pragma mark - public interface
- (void)start {
[_timer setFireDate:[NSDate date]];
}
- (void)pause {
[_timer setFireDate:[NSDate distantFuture]];
}
- (void)stop {
[_timer invalidate];
_timer = nil;
}
#pragma mark - getters / setters
- (UIView *)view2 {
if (!_view2) {
_view2 = [[UIView alloc]initWithFrame:self.bounds];
_view2.backgroundColor = [UIColor whiteColor];
[self addSubview:_view2];
}
return _view2;
}
- (UIView *)view1 {
if (!_view1) {
_view1 = [[UIView alloc] initWithFrame:self.bounds];
_view1.backgroundColor = [UIColor whiteColor];
[self addSubview:_view1];
}
return _view1;
}
#pragma mark - others
- (void)swapView {
UIView *view = self.view1;
self.view1 = self.view2;
self.view2 = view;
}
@end
以上就是完整的实现代码,说明两个注意点:
- offset之所以要除以2,是因为CGAffineTransformMakeScale是中心缩放的,缩放完视图已经居中了,要移到边界只需要平移一半就可以了
- 动画时间一定比timer间隔时间要短一点点,不然出现动画没做完,下一个timer的迭代又来了,出现不符合预期的效果
好了,需要的同学自行copy吧_