iOS CALayer實現按鈕的流光效果

前言

hihi,勇敢的小夥伴兒們大家好,今天想給大家分享的是一個動畫效果,我在簡書上看的,參考文章地址,怎麼說呢,我看了代碼,實現是很簡單的,但是呢,這個代碼裏硬性條件比較多,比如說需要準備幾個圖片資源,所以我選擇了與之有所差異的方式實現,儘量減少圖片的使用,當然,我這個效果是比較簡單的,如圖:

所以纔可以這樣做,如果換做參考文章博主的風格,用這種簡單的方式的確很難實現,還需要設計師的支持。如圖:

雖然我這個很簡單,但我還是要分享給有需要的小夥伴兒們~

這裏主要是組合動畫,一個是後面閃爍一下的輪廓感,一個是前面閃爍一下的流光,話不多說上代碼~

正文

首先我們需要研究一下這個按鈕或者View的組成。

這裏自定義了一個View叫做FlowButton,它由兩個子視圖組成,一部分是有點擊事件的按鈕UIButton,一部分是提供流光效果的圖片UIImageView,那負責閃爍的輪廓光我們不放在子視圖裏,原因是輪廓是由layer的shadow的效果形成的輪廓感,如果被包含在FlowButton裏面,shadow被裁剪了,就沒有陰影效果了。

分析到這裏,着手實現。

FlowButton.h

#import <UIKit/UIKit.h>

@interface FlowButton : UIView

@property (nonatomic, strong) UIButton *flowLightBtn; //按鈕
@property (nonatomic, strong) UIImageView *flowLightImageView; //流光圖

@end

FlowButton.m


#import "FlowButton.h"

@implementation FlowButton

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        //按鈕
        self.flowLightBtn = [[UIButton alloc] init];
        //這裏設置默認顏色 無需一致
        [self.flowLightBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [self.flowLightBtn setTitleColor:[UIColor lightTextColor] forState:UIControlStateHighlighted];
        //添加約束必要條件
        self.flowLightBtn.translatesAutoresizingMaskIntoConstraints = NO;
        [self addSubview:self.flowLightBtn];
        
        //流光
        self.flowLightImageView = [[UIImageView alloc] init];
        //設立設置默認顏色 無需一致
        self.flowLightImageView.backgroundColor = [UIColor whiteColor];
        //添加約束必要條件
        self.flowLightImageView.translatesAutoresizingMaskIntoConstraints = NO;
        //先將它隱藏 開啓動畫的時候再顯示 沒有動畫的時候不需要添加輪廓光
        self.flowLightImageView.hidden = YES;
        //不能影響按鈕的點擊效果 有備無患
        self.flowLightImageView.userInteractionEnabled = YES;
        [self addSubview:self.flowLightImageView];
        
        //約束
        //flowLightBtn的Top與父視圖的Top間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightBtn attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
        //flowLightBtn的Bottom與父視圖的Bottom間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightBtn attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
        //flowLightBtn的Left與父視圖的Left間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightBtn attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
        //flowLightBtn的Right與父視圖的Right間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightBtn attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
        
        //flowLightImageView的Top與父視圖的Top間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightImageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
        //flowLightImageView的Bottom與父視圖的Bottom間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightImageView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
        //flowLightImageView的Left與父視圖的Left間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightImageView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
        //flowLightImageView的Right與父視圖的Right間距爲0
        [self addConstraint:[NSLayoutConstraint constraintWithItem:self.flowLightImageView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
    }
    return self;
}

@end

調用的時候注意我們要手動添加一個View負責給FlowButton增加一個輪廓光,說白了就是給View的Layer設置一個shadow,並給view添加一個透明度改變的動畫。

這裏需要一個漸變色的png來作爲遮罩,疊加出各種顏色的漸變效果。如圖:

ViewController.m

#import "ViewController.h"
#import "FlowButton.h"

@interface ViewController () {
    UIView *flowView; //輪廓光View
    FlowButton *flowButton; //FlowButton
    UIView *flowView2; //輪廓光View
    FlowButton *flowButton2; //FlowButton
    NSTimer *timer;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //設置背景色
    self.view.backgroundColor = [UIColor colorWithRed:73.0 / 255.0 green:135.0 / 255.0 blue:244.0 / 255.0 alpha:1.0];
    
    {
        //輪廓光View
        flowView = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 200, 50)];
        //圓角 此處不加masksToBounds防止陰影被切掉
        flowView.layer.cornerRadius = 2.0;
        //背景顏色
        flowView.backgroundColor = [UIColor whiteColor];
        //陰影顏色
        flowView.layer.shadowColor = [UIColor whiteColor].CGColor;
        //陰影在四周包圍
        flowView.layer.shadowOffset = CGSizeMake(0,0);
        //陰影透明度
        flowView.layer.shadowOpacity = 1.0;
        //陰影的半徑
        flowView.layer.shadowRadius = 5;
        //View的透明度 默認是0.0
        flowView.alpha = 0.0;
        //添加到self.view上
        [self.view addSubview:flowView];
        
        //flowButton
        flowButton = [[FlowButton alloc] initWithFrame:CGRectMake(100, 300, 200, 50)];
        //設置Button的字體
        flowButton.flowLightBtn.titleLabel.font = [UIFont fontWithName:@"Avenir-Medium" size:15.5f];
        //設置背景顏色
        flowButton.backgroundColor = [UIColor colorWithRed:242.0 / 255.0 green:2.0 / 255.0 blue:14.0 / 255.0 alpha:1.0];
        //設置Button的title
        [flowButton.flowLightBtn setTitle:NSLocalizedString(@"Purchase", nil) forState:UIControlStateNormal];
        //設置圓角
        flowButton.layer.cornerRadius = 2.0;
        flowButton.layer.masksToBounds = YES;
        //不能作爲flowView的子視圖存在 會一起執行透明度變化的動畫
        [self.view addSubview:flowButton];
    }
    {
        //輪廓光View
        flowView2 = [[UIView alloc] initWithFrame:CGRectMake(100, 400, 80, 50)];
        //圓角 此處不加masksToBounds防止陰影被切掉
        flowView2.layer.cornerRadius = 2.0;
        //背景顏色
        flowView2.backgroundColor = [UIColor whiteColor];
        //陰影顏色
        flowView2.layer.shadowColor = [UIColor whiteColor].CGColor;
        //陰影在四周包圍
        flowView2.layer.shadowOffset = CGSizeMake(0,0);
        //陰影透明度
        flowView2.layer.shadowOpacity = 1.0;
        //陰影的半徑
        flowView2.layer.shadowRadius = 5;
        //View的透明度 默認是0.0
        flowView2.alpha = 0.0;
        //添加到self.view上
        [self.view addSubview:flowView2];
        
        //flowButton
        flowButton2 = [[FlowButton alloc] initWithFrame:CGRectMake(100, 400, 80, 50)];
        //設置Button的字體
        flowButton2.flowLightBtn.titleLabel.font = [UIFont fontWithName:@"Avenir-Medium" size:15.5f];
        //設置背景顏色
        flowButton2.backgroundColor = [UIColor colorWithRed:59.0 / 255.0 green:214.0 / 255.0 blue:73.0 / 255.0 alpha:1.0];
        //設置Button的title
        [flowButton2.flowLightBtn setTitle:NSLocalizedString(@"Purchase", nil) forState:UIControlStateNormal];
        //設置圓角
        flowButton2.layer.cornerRadius = 2.0;
        flowButton2.layer.masksToBounds = YES;
        //不能作爲flowView的子視圖存在 會一起執行透明度變化的動畫
        [self.view addSubview:flowButton2];
    }
    
    //開啓計時器每隔5s執行一次動畫
    timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(showAnimations) userInfo:nil repeats:YES];
}

- (void)showAnimations {
    [self showAnimationsWithView:flowView Button:flowButton Width:200];
    [self showAnimationsWithView:flowView2 Button:flowButton2 Width:80];
}

//流光動畫
- (void)showAnimationsWithView:(UIView *)backView Button:(FlowButton *)buttonView  Width:(CGFloat)buttonViewWidth {
    //輪廓光呼吸動畫
    CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    //所改變屬性的起始值
    opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0];
    //所改變屬性的結束時的值
    opacityAnimation.toValue = [NSNumber numberWithFloat:0.0];
    //動畫的時長
    opacityAnimation.duration = 1.0;
    //動畫結束時是否執行逆動畫
    opacityAnimation.autoreverses = NO;
    //重複的次數。不停重複設置爲 HUGE_VALF
    opacityAnimation.repeatCount = 0;
    //爲輪廓光View添加layer動畫
    [backView.layer addAnimation:opacityAnimation forKey:@"opacity"];
    
    //流光動畫
    //爲flowLightImageView新建一個Layer作爲遮罩層mask
    CALayer *maskLayer = [CALayer layer];
    UIImage *maskImage = [UIImage imageNamed:@"btnLightMask.png"];
    maskLayer.frame = CGRectMake(0, 0, maskImage.size.width, maskImage.size.height);
    //設置contents(一般該對象是一個CGImageRef對象,可通過contentsGravity屬性設置圖片展示模式,是平鋪整個bounds還是等比例展示)。
    maskLayer.contents = (__bridge id)maskImage.CGImage;
    //爲了讓maskLayer和flowLightImageView兩個圖層搭配出一個新的視覺效果,簡單理解就是一個遮罩,mask圖層區域外的任何區域不顯示。
    buttonView.flowLightImageView.layer.mask = maskLayer;
    //將隱藏的流光ImageView顯示出來
    buttonView.flowLightImageView.hidden = NO;
    
    //x軸方向的位移動畫
    CABasicAnimation *flowAnimation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    //由於按鈕可長可短 所以在這裏需要進行判斷
    CGFloat width = maskLayer.frame.size.width > buttonViewWidth ? maskLayer.frame.size.width : buttonViewWidth;
    //所改變屬性的起始值
    flowAnimation.fromValue = [NSNumber numberWithFloat:- width / 2.];
    //所改變屬性的結束時的值
    flowAnimation.toValue = [NSNumber numberWithFloat:buttonViewWidth];
    //動畫的時長
    flowAnimation.duration = 1.0;
    //重複的次數。不停重複設置爲 HUGE_VALF
    flowAnimation.repeatCount = 0;
    //防止動畫結束後回到初始狀態
    flowAnimation.removedOnCompletion = NO;
    flowAnimation.fillMode = kCAFillModeForwards;
    //添加動畫
    [maskLayer addAnimation:flowAnimation forKey:@"transform.translation.x"];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    //視圖消失的時候將timer釋放
    [timer invalidate];
    timer = nil;
}

@end

關於CABasicAnimation可移步CAAnimation的總結學習。其中關於CALayer中的mask瞭解可移步CALayer之mask使用從此不再糾結,這篇文章裏不作贅述。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章