循环仿折叠动画

最近项目里有这样一个需求:在搜索人的时候,提示一些信息,这些信息用折叠的方式把一条把上一条推掉,类似立方体翻转。具体效果可以如下图:

效果图.gif

刚好的这个效果在之前一篇博客里看过:iOS动画-Transform和KeyFrame动画,这篇文章大家可以看下具体原理介绍,本篇只说下一些注意点。不过他那个动画只做一次,产品需要做成循环的,然后想着看能不能做成可以4个方向的,就封装了下,实现效果如下:

向下.gif

向上.gif
向右.gif
向左.gif

那要封装的话,就需要提供动画的开始,暂停,结束的接口,同时有一个初始化方法。那么大致就提炼出了以下视图头文件:

#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

以上就是完整的实现代码,说明两个注意点:

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,362评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,010评论 4 62
  • 我很努力的想写好这条信息! 可语言在此刻显得特别苍白无力! 2015年9月,我给一岁的宝宝断奶,开启了创业之路,理...
    梁明月创业笔记阅读 157评论 5 4
  • 暮春三月,江南草长,回眸处,云燕呢喃似无声。——《暮春》 一幅画面展示温情时光。
    桔纱阅读 475评论 0 9
  • 早起醒来去黄河宾馆找老舅,早餐的热面皮真难吃,没有家里的好。酸豆腐一直吃不惯,可能还是不适应这种豪放的吃法。 到了...
    南诏阅读 182评论 0 0