美文网首页
循环仿折叠动画

循环仿折叠动画

作者: TerryD | 来源:发表于2016-08-22 19:47 被阅读81次

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

    效果图.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吧_

    相关文章

      网友评论

          本文标题:循环仿折叠动画

          本文链接:https://www.haomeiwen.com/subject/hkjgsttx.html