1. 程式人生 > >iOS模仿安卓Material Design的漣漪動畫按鈕

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