美文网首页iOS进阶iOS学习征服iOS
iOS模仿安卓Material Design的涟漪动画按钮

iOS模仿安卓Material Design的涟漪动画按钮

作者: DeepChafferer | 来源:发表于2017-04-01 16:35 被阅读304次

    首先来看看实现后的效果吧

    demo.gif

    实现思路

    其实这个按钮的实现时分简单,我的思路是:

    • 用UIView + UITapGestureRecognizer 来模拟实现一个按钮的效果
    • 记录每次手指点击的位置,以该位置为圆心用CALayer画圆
    • 采用block回调实现点击事件
    • 采用CAAnimationGroup来实现组合动画(涟漪效果)
    • 最后在CAAnimationDelegate中- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;方法中消除动画。

    是不是很简单?
    下面我们来看下代码实现

    //在ZYCRippleButton.h文件中
    #import <UIKit/UIKit.h>
    
    typedef void (^ZYCRippleButtonBlock)(void);
    
    @interface ZYCRippleButton : UIView
    //用于模拟button的tittle
    @property (nonatomic, strong) UILabel *textLabel;
    //“涟漪”颜色
    @property (nonatomic, strong) UIColor *rippleColor;
    //“涟漪”的粗细
    @property (nonatomic, assign) NSUInteger rippleLineWidth;
    //点击回调block
    @property (nonatomic, copy)   ZYCRippleButtonBlock rippleBlock;
    
    - (void) setButtonTittle:(NSString *)tittle;
    - (void) setButtonTittleColor:(UIColor *)tColor;
    - (void) setButtonBackgroundColor:(UIColor *)bgColor;
    - (void) setButtonTittle:(NSString *)tittle withTittleColor:(UIColor *)tColor;
    - (void) setButtonTittle:(NSString *)tittle withTittleColor:(UIColor *)tColor backgroundColor:(UIColor *)bgColor;
    
    @end
    
    //在ZYCRippleButton.m文件中
    #import "ZYCRippleButton.h"
    
    const CGFloat ZYCRippleInitialRaius = 20;
    
    @interface ZYCRippleButton()<CAAnimationDelegate>
    
    @end
    
    @implementation ZYCRippleButton
    
    - (instancetype)initWithFrame:(CGRect)frame{
        if (self == [super initWithFrame:frame]){
            [self initZYCRippleButton];
        }
        return self;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder{
        if (self == [super initWithCoder:aDecoder]){
            [self initZYCRippleButton];
        }
        return self;
    }
    
    #pragma mark - init
    - (void)initZYCRippleButton{
        //模拟按钮点击效果
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapped:)];
        [self addGestureRecognizer:tap];
        //初始化label
        self.textLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.textLabel.backgroundColor = [UIColor clearColor];
        self.textLabel.textColor = [UIColor blackColor];
        self.textLabel.textAlignment = NSTextAlignmentCenter;
        self.textLabel.text = @"button";
        [self addSubview:_textLabel];
        
        self.backgroundColor = [UIColor lightGrayColor];
        self.clipsToBounds = YES;
    }
    
    - (void) setButtonTittle:(NSString *)tittle{
        self.textLabel.text = tittle;
    }
    
    - (void) setButtonTittleColor:(UIColor *)tColor{
        self.textLabel.textColor = tColor;
    }
    
    - (void) setButtonBackgroundColor:(UIColor *)bgColor{
        self.backgroundColor = bgColor;
    }
    
    - (void) setButtonTittle:(NSString *)tittle withTittleColor:(UIColor *)tColor{
        [self setButtonTittle:tittle withTittleColor:tColor backgroundColor:nil];
    }
    
    - (void) setButtonTittle:(NSString *)tittle withTittleColor:(UIColor *)tColor backgroundColor:(UIColor *)bgColor{
        if (tittle){
            self.textLabel.text = tittle;
        }
        if (tColor){
            self.textLabel.textColor = tColor;
        }
        if (bgColor){
            self.backgroundColor = bgColor;
        }
    }
    
    #pragma mark - tapped
    - (void)tapped:(UITapGestureRecognizer *)tap{
        //获取所点击的那个点
        CGPoint tapPoint = [tap locationInView:self];
        //创建涟漪
        CAShapeLayer *rippleLayer = nil;
        CGFloat buttonWidth = self.frame.size.width;
        CGFloat buttonHeight = self.frame.size.height;
        CGFloat bigBoard = buttonWidth >= buttonHeight ? buttonWidth : buttonHeight;
        CGFloat smallBoard = buttonWidth <= buttonHeight ? buttonWidth : buttonHeight;
        CGFloat rippleRadiius = smallBoard/2 <= ZYCRippleInitialRaius ? smallBoard/2 : ZYCRippleInitialRaius;
        
        CGFloat scale = bigBoard / rippleRadiius + 0.5;
        
        rippleLayer = [self createRippleLayerWithPosition:tapPoint rect:CGRectMake(0, 0, rippleRadiius * 2, rippleRadiius * 2) radius:rippleRadiius];
        
        [self.layer addSublayer:rippleLayer];
        
        //layer动画
        CAAnimationGroup *rippleAnimationGroup = [self createRippleAnimationWithScale:rippleRadiius duration:1.5f];
        //使用KVC消除layer动画以防止内存泄漏
        [rippleAnimationGroup setValue:rippleLayer forKey:@"rippleLayer"];
        
        [rippleLayer addAnimation:rippleAnimationGroup forKey:nil];
        rippleLayer.delegate = self;
        
    }
    
    #pragma mark - createRippleLayer && CAAnimationGroup
    - (CAShapeLayer *)createRippleLayerWithPosition:(CGPoint)position rect:(CGRect)rect radius:(CGFloat)radius{
        CAShapeLayer *layer = [CAShapeLayer layer];
        layer.path = [self createPathWithRadius:rect radius:radius];
        layer.position = position;
        layer.bounds = CGRectMake(0, 0, radius * 2, radius * 2);
        layer.fillColor = self.rippleColor ? self.rippleColor.CGColor : [UIColor whiteColor].CGColor;
        layer.opacity = 0;
        layer.lineWidth = self.rippleLineWidth ? self.rippleLineWidth : 1;
        
        return layer;
    }
                                                  
    - (CAAnimationGroup *)createRippleAnimationWithScale:(CGFloat)scale duration:(CGFloat)duration{
        CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
        scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
        scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(scale, scale, 1)];
        
        CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        alphaAnimation.fromValue = @0.5;
        alphaAnimation.toValue = @0;
        
        CAAnimationGroup *animation = [CAAnimationGroup animation];
        animation.animations = @[scaleAnimation, alphaAnimation];
        animation.delegate = self;
        animation.duration = duration;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
        
        return animation;
    }
    
    - (CGPathRef)createPathWithRadius:(CGRect)frame radius:(CGFloat)radius{
        return [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:radius].CGPath;
    }
    
    #pragma mark - CAAnimationDelegate
    - (void)animationDidStart:(CAAnimation *)anim{
        if (self.rippleBlock){
            self.rippleBlock();
        }
    }
    
    - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
        CALayer *layer = [anim valueForKey:@"rippleLayer"];
        if (layer) {
            [layer removeFromSuperlayer];
        }
    }
                                                  
    
    @end
    
    

    以上就完成了一个自定义的模仿安卓Material Design按钮点击的iOS版按钮
    让我们来测试下吧

    @interface ViewController ()
    
    @property (nonatomic, strong) ZYCRippleButton *rippleButton;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.rippleButton = [[ZYCRippleButton alloc]initWithFrame:CGRectMake((self.view.frame.size.width - 300)/2, 100 , 300, 100)];
        self.rippleButton.rippleLineWidth = 1;
        self.rippleButton.rippleColor = [UIColor whiteColor];
        [self.rippleButton setButtonTittle:@"测试按钮1" withTittleColor:[UIColor whiteColor] backgroundColor:[UIColor colorWithRed:36.0f/255.0f green:188.0f/255.0f blue:255.0f/255.0f alpha:0.5]];
        __block typeof(NSInteger) tapNum = 0;
        __block typeof(self) bself = self;
        self.rippleButton.rippleBlock = ^(void){
            [bself.rippleButton setButtonTittle:[NSString stringWithFormat:@"点击第%ld次",(long)tapNum++]];
        };
        [self.view addSubview:_rippleButton]; 
    }
    
    - (void)onClick:(NSInteger)i{
        [self.rippleButton setButtonTittle:[NSString stringWithFormat:@"点击%ld次",(long)i++]];
    }
    
    @end
    

    下面附上项目github地址
    点我下载(喜欢的话记得给我个star哦_)

    相关文章

      网友评论

      • Gizone_iac:我尝试这把这段代码加载UIbutton 的TouchBegan里。 rippleLayer.delegate = self; 会导致崩溃。 而且动画的效果并不会被所在区域内被限制。 会扩散到按钮之外的地方
        Gizone_iac:@DeepChafferer 很奇怪的就是我用UIButton 执行 rippleLayer.delegate = self; 就会崩溃。 而且没有任何崩溃信息。动画效果扩散在我设置masksToBounds 之后就好了。 谢谢解答
        DeepChafferer:我采用的是UIView + 手势来实现一个模仿安卓MD设计规范的按钮,你设置代理崩溃具体的崩溃信息是什么呢,动画效果会扩散出去应该是你clipToBounds这个属性没有设置好。

      本文标题:iOS模仿安卓Material Design的涟漪动画按钮

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