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

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

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哦_)

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

推荐阅读更多精彩内容