美文网首页别人的iOS精华
iOS 动画 —— Hamburger-button

iOS 动画 —— Hamburger-button

作者: 天空中的球 | 来源:发表于2016-08-04 01:10 被阅读322次

    看了 hamburger-button 这个的实现后,一直很喜欢,在此跟随着How to build a nice Hamburger Button transition in Swift 用 OC 实现一下。

    hamburger-button

    通过看动画图,我们可以大致分析思路:

    1、画出怎样的线和圈圈?
    2、动画是如何过渡转换的?
    

    当然叉叉是通过两条线转化过来的,此时我们通过源码中提供的数值,它这个数值,是根据是它设计时候的确定的。

    一、画三条线

    画三条线,实际上是先画两条线 ,再截取一条线,图片和路径如下:

    两条线

    两条线
    CGMutablePathRef path = CGPathCreateMutable(); 
    CGPathMoveToPoint(path, nil, 2, 2);
    CGPathAddLineToPoint(path, nil, 28, 2);
    
    

    PS :CGContextRef,CGPath 和 UIBezierPath 本质上都是一样的,都是使用Quartz来绘画,只不过把绘图操作暴露在不同的API层面上。此处用的CGPath。

    中间那条线

    另一条线是通过截取下面这个圈圈的前半部分确定的

    圈圈 原始图
    CGMutablePathRef  path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, 10, 27);
    CGPathAddCurveToPoint(path, nil, 12.00, 27.00, 28.02, 27.00, 40, 27);
    CGPathAddCurveToPoint(path, nil, 55.92, 27.00, 50.47,  2.00, 27,  2);
    CGPathAddCurveToPoint(path, nil, 13.16,  2.00,  2.00, 13.16,  2, 27);
    CGPathAddCurveToPoint(path, nil,  2.00, 40.84, 13.16, 52.00, 27, 52);
    CGPathAddCurveToPoint(path, nil, 40.84, 52.00, 52.00, 40.84, 52, 27);
    CGPathAddCurveToPoint(path, nil, 52.00, 13.16, 42.39,  2.00, 27,  2);
    CGPathAddCurveToPoint(path, nil, 13.16,  2.00,  2.00, 13.16,  2, 27);
    

    截取那条线

    layer.strokeStart = 0.028f;
    layer.strokeEnd = 0.111f;
    

    不过先不纠结这些数值怎么来的,其中提到一点是像那种圆可以通过PaintCode 得出,当然也是可以自己算的。

    二、过渡动画

    在CABasicAnimation中通过设定起始点,终点,时间,动画会沿着你这设定点进行移动。此处动画的核心转换是 toValue 的变化来达到效果。

    两条线变成叉叉,或者叉叉返回两条线

    叉叉的线

    变成 叉叉

     CATransform3D translation = CATransform3DMakeTranslation(-4, 0, 0);
     topTransform.toValue = [NSValue valueWithCATransform3D: CATransform3DRotate(translation, -0.7853975, 0, 0, 1)];
     bottomTransform.toValue = [NSValue valueWithCATransform3D: CATransform3DRotate(translation, 0.7853975, 0, 0, 1)];
    

    叉叉变回来两条线

    topTransform.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    bottomTransform.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    

    单线变成圈圈,圈圈变成线条

    圈圈的改变

    变成圈圈

    strokeStart.toValue = @(0.325);
    strokeEnd.toValue = @( 0.9f);
    

    变成线条

    strokeStart.toValue = 0.028f;
    strokeEnd.toValue = 0.111f;
    

    然后两者结合起来,就达到了我们的那个效果啦,下面通过具体的实现代码来看。

    直接上代码:
    #import <UIKit/UIKit.h>
    
    @interface HamburgerButton : UIButton
    
    @property (nonatomic, assign) BOOL showsMenu;
    
    @end
    
    
    #import "HamburgerButton.h"
    
    static const CGFloat kMenuStrokeStart = 0.325f;
    static const CGFloat kMenuStrokeEnd = 0.9f;
    static const CGFloat kHamburgerStrokeStart = 0.028f;
    static const CGFloat kHamburgerStrokeEnd = 0.111f;
    
    @interface HamburgerButton ()
    
    @property (nonatomic, assign) CGMutablePathRef shortStrokePath;
    @property (nonatomic, assign) CGMutablePathRef outlinePath;
    @property (nonatomic, strong) CAShapeLayer *topLayer;
    @property (nonatomic, strong) CAShapeLayer *middleLayer;
    @property (nonatomic, strong) CAShapeLayer *bottomLayer;
    
    @end
    
    @implementation HamburgerButton
    
    
    - (id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self configLayer];
        }
        return self;
    }
    
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self configLayer];
        }
        return self;
    }
    
    - (void)configLayer {
        // init
        self.topLayer = [CAShapeLayer layer];
        self.topLayer.path = self.shortStrokePath;
        self.middleLayer = [CAShapeLayer layer];
        self.middleLayer.path = self.outlinePath;
        self.bottomLayer = [CAShapeLayer layer];
        self.bottomLayer.path = self.shortStrokePath;
    
        // 添加属性
        for (CAShapeLayer *layer in @[self.topLayer, self.middleLayer, self.bottomLayer]) {
            // 设置基本属性
            layer.fillColor = nil;
            layer.strokeColor = [UIColor whiteColor].CGColor;
            layer.lineWidth = 4.f;
            layer.miterLimit = 4.f;
            layer.lineCap = kCALineCapRound;
            layer.masksToBounds = YES;
            // 创建一个共享的路径
            CGPathRef strokingPath = CGPathCreateCopyByStrokingPath(layer.path, nil, 4, kCGLineCapRound,kCGLineJoinMiter, 4);
            layer.bounds = CGPathGetPathBoundingBox(strokingPath);
            layer.actions = @{
                              @"strokeStart":[NSNull null],
                              @"strokeEnd":[NSNull null],
                              @"transform":[NSNull null]
                              };
            // 添加到 layer 上
            [self.layer addSublayer:layer];
    
        }
        // 确定其位置
        self.topLayer.anchorPoint = CGPointMake(28.0/30.0, 0.5);
        self.topLayer.position = CGPointMake(40, 18);
        
        self.middleLayer.position = CGPointMake(27, 27);
        self.middleLayer.strokeStart = kHamburgerStrokeStart;
        self.middleLayer.strokeEnd = kHamburgerStrokeEnd;
        
        self.bottomLayer.anchorPoint = CGPointMake(28.0 / 30.0, 0.5);
        self.bottomLayer.position = CGPointMake(40, 36);
    }
    
    
    - (void)setShowsMenu:(BOOL)showsMenu {
        
        // 中间那条线的处理
        CABasicAnimation *strokeStart = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
        CABasicAnimation *strokeEnd = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        /**
         *  注意CAMediaTimingFunction 是一个贝塞尔曲线的控制方法,可以令动画做到先慢後快或先快後慢的结果
         */
        if (showsMenu) {
            strokeStart.toValue = @(kMenuStrokeStart);
            strokeStart.duration = 0.5;
            strokeStart.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.25 :-0.4 :0.5 :1];
            
            strokeEnd.toValue = @(kMenuStrokeEnd);
            strokeEnd.duration = 0.6;
            strokeEnd.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.25 :-0.4 :0.5 :1];
        }else {
            strokeStart.toValue = @(kHamburgerStrokeStart);
            strokeStart.duration = 0.5;
            strokeStart.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.25: 0 : 0.5 : 1.2];
            strokeStart.beginTime = CACurrentMediaTime() + 0.1;
            strokeStart.fillMode = kCAFillModeBackwards;
            
            strokeEnd.toValue = @(kHamburgerStrokeEnd);
            strokeEnd.duration = 0.6;
            strokeEnd.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.25 : 0.3 : 0.5 : 0.9];
        }
        
        [self addAnimationWithLayer:self.middleLayer animation:strokeStart];
        [self addAnimationWithLayer:self.middleLayer animation:strokeEnd];
        
        // 底部和上部的线
        CABasicAnimation *topTransform = [CABasicAnimation animationWithKeyPath:@"transform"];
        topTransform.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.5 :-0.8 :0.5 :1.85];
        topTransform.duration = 0.4;
        topTransform.fillMode = kCAFillModeBackwards;
        /**
         *  CATransform3D 动作效果
         */
        
        CABasicAnimation *bottomTransform = topTransform.copy;
        if (showsMenu) {
            CATransform3D translation = CATransform3DMakeTranslation(-4, 0, 0);
            topTransform.toValue = [NSValue valueWithCATransform3D: CATransform3DRotate(translation, -0.7853975, 0, 0, 1)];
            topTransform.beginTime = CACurrentMediaTime() + 0.25;
            
            bottomTransform.toValue = [NSValue valueWithCATransform3D: CATransform3DRotate(translation, 0.7853975, 0, 0, 1)];
            bottomTransform.beginTime = CACurrentMediaTime() + 0.25;
        } else {
            topTransform.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
            topTransform.beginTime = CACurrentMediaTime() + 0.05;
            
            bottomTransform.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
            bottomTransform.beginTime = CACurrentMediaTime() + 0.05;
        }
        
        [self addAnimationWithLayer:self.topLayer animation:topTransform];
        [self addAnimationWithLayer:self.bottomLayer animation:bottomTransform];
    
    }
    
    - (void)addAnimationWithLayer:(CAShapeLayer *)layer animation:(CABasicAnimation *)animation{
        
        if (animation.fromValue == nil) {
            animation.fromValue = [layer.presentationLayer valueForKeyPath:animation.keyPath];
        }
        [layer addAnimation:animation forKey:animation.keyPath];
        // 记住需要重新设置一下,让其达到实现效果
        [layer setValue:animation.toValue forKey:animation.keyPath];
    }
    
    - (CGMutablePathRef)shortStrokePath {
        if (!_shortStrokePath) {
            _shortStrokePath = CGPathCreateMutable();
            CGPathMoveToPoint(_shortStrokePath, nil, 2, 2);
            CGPathAddLineToPoint(_shortStrokePath, nil, 28, 2);
        }
        return _shortStrokePath;
    }
    
    - (CGMutablePathRef)outlinePath {
        if (!_outlinePath) {
            _outlinePath = CGPathCreateMutable();
            CGPathMoveToPoint(_outlinePath, nil, 10, 27);
            CGPathAddCurveToPoint(_outlinePath, nil, 12.00, 27.00, 28.02, 27.00, 40, 27);
            CGPathAddCurveToPoint(_outlinePath, nil, 55.92, 27.00, 50.47,  2.00, 27,  2);
            CGPathAddCurveToPoint(_outlinePath, nil, 13.16,  2.00,  2.00, 13.16,  2, 27);
            CGPathAddCurveToPoint(_outlinePath, nil,  2.00, 40.84, 13.16, 52.00, 27, 52);
            CGPathAddCurveToPoint(_outlinePath, nil, 40.84, 52.00, 52.00, 40.84, 52, 27);
            CGPathAddCurveToPoint(_outlinePath, nil, 52.00, 13.16, 42.39,  2.00, 27,  2);
            CGPathAddCurveToPoint(_outlinePath, nil, 13.16,  2.00,  2.00, 13.16,  2, 27);
        }
        return _outlinePath;
    }
    
    @end
    
    
    #import "ViewController.h"
    #import "HamburgerButton.h"
    
    @interface ViewController ()
    
    @property (nonatomic, strong) HamburgerButton *hamburgerButton;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor colorWithRed:38.0 / 255 green:151.0 / 255 blue:68.0 / 255 alpha:1.0];
        self.hamburgerButton.center = self.view.center;
        [self.view addSubview:self.hamburgerButton];
    }
    
    - (void)hamburgerAction:(HamburgerButton *)button {
        button.selected = !button.selected;
        button.showsMenu = button.selected;
    }
    
    - (HamburgerButton *)hamburgerButton {
        if (!_hamburgerButton) {
            _hamburgerButton = [[HamburgerButton alloc] initWithFrame:CGRectMake(0, 0, 54, 54)];
            [_hamburgerButton addTarget:self action:@selector(hamburgerAction:) forControlEvents:UIControlEventTouchUpInside];
        }
        return _hamburgerButton;
    }
    
    @end
    
    

    总的来说大致实现是这样的,但里面其实还有很多点可以挖掘。

    • 数值(toValue)是如何确定的?
    • CAMediaTimingFunction 如何改变动画速度?
    • CATransform3D 动作效果如何设置细节的?

    这个先记录着,还是继续通过动画一步一步先实现效果,然后再慢慢深入细节点。

    相关文章

      网友评论

      • 不负时光_:您好,我想讲最终的圆圈变小点,该怎么做?outlinePath,这里面的参数不好修改
        天空中的球:恩恩,这个可以需要效果可以用 PrintCode 将其效果呈现出来,直接算修改确实不好弄
      • 鱼香肉丝_我鱼呢:ayer?.actions = [
        "strokeStart": NSNull(),
        "strokeEnd": NSNull(),
        "transform": NSNull()
        ]
        能分析下这句代码什么作用吗,没有这句代码动画效果就变得很糟糕了,而且时间不受控制
        天空中的球:这相当于设置(0,0) 点那种,strokeStart和strokeEnd 是一个0-1的取值范围,表示一段路径的开始和结尾,可以理解需要有终的必须先有始的吧
      • 是花落呀:在addAnimationWithLayer 方法里的if语句里面valueForKeyPath的返回值还需要赋给animation.fromValue

        animation.fromValue = [layer.presentationLayer valueForKeyPath:animation.keyPath];
        天空中的球:@littleStu 对的,谢谢你

      本文标题:iOS 动画 —— Hamburger-button

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