解读RQShineLabel动画效果

作者: H的幻想世界 | 来源:发表于2016-11-30 16:46 被阅读293次

    在github上看到一个文字闪烁随机渐入渐出的动画效果,跟着写了一遍,把自己对代码的理解加上了注释,发出来希望和大家共同学习,有理解错的地方欢迎指正。

    地址 : https://github.com/zipme/RQShineLabel

    rqshinelabel.gif
    #import <UIKit/UIKit.h>
    
    @interface RQShineLabel: UILabel
    
    //渐进时间
    @property (assign, nonatomic, readwrite) CFTimeInterval shineDuration;
    //渐出时间
    @property (assign, nonatomic, readwrite) CFTimeInterval fadeoutDuration;
    //开始动画,默认为NO
    @property (assign, nonatomic, readwrite, getter = isAutoStart) BOOL autoStart;
    //结束动画
    @property (assign, nonatomic, readonly, getter = isShining) BOOL shining;
    //是否看得到
    @property (assign, nonatomic, readonly, getter = isVisible) BOOL visible;
    
    //start the animation
    
    - (void)shine;
    - (void)shineWithCompletion:(void(^)())completion;
    - (void)fadeOut;
    - (void)fadeOutWithCompletion:(void(^)())completion;
    
    #import "RQShineLabel.h"
    
    @interface RQShineLabel()
    
    @property (nonatomic, strong) NSMutableAttributedString *attributedString;
    @property (nonatomic, strong) NSMutableArray *characterAnimationDurations;
    @property (nonatomic, strong) NSMutableArray *characterAnimationDelays;
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (assign, nonatomic) CFTimeInterval beginTime;
    @property (assign, nonatomic) CFTimeInterval endTime;
    @property (assign, nonatomic, getter=isFadedOut) BOOL fadedOut;
    @property (nonatomic, copy) void (^completion)();
    
    @end
    
    @implementation RQShineLabel
    - (instancetype)init
    {
        self = [super init];
        if (!self) {
            return nil;
        }
        
        [self commonInit];
        
        return self;
    }
    
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (!self) {
            return nil;
        }
        
        [self commonInit];
        
        return self;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        self = [super initWithCoder:aDecoder];
        if (!self) {
            return nil;
        }
        [self commonInit];
        
        [self setText:self.text];
        
        return self;
    }
    
    //公共部分
    - (void)commonInit
    {
        //渐进
        _shineDuration = 2.5;
        //渐出
        _fadeoutDuration = 2.5;
        //开启
        _autoStart = NO;
        //结束
        _fadedOut = YES;
        //文字颜色
        self.textColor = [UIColor whiteColor];
        
        _characterAnimationDurations = [NSMutableArray array];
        _characterAnimationDelays = [NSMutableArray array];
        
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAttributedString)];
       
        _displayLink.paused = YES;
        //添加到运行循环 模式是可以同时做两件事
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    //显示到window时调用
    - (void)didMoveToWindow
    {
        if (nil != self.window && self.autoStart) {
            [self shine];
        }
    
    }
    
    - (void)setText:(NSString *)text
    {
        //创建字符属性
        self.attributedText = [[NSAttributedString alloc]initWithString:text];
    }
    
    - (void)setAttributedText:(NSAttributedString *)attributedText
    {
        //自定义字符属性
        self.attributedString = [self initiaAttributedStringFromAttributedString:attributedText];
        //将自定义字符串赋给父类
        [super setAttributedText:self.attributedString];
        for (NSUInteger i = 0; i < attributedText.length; i ++) {
            //随机推迟字符
            self.characterAnimationDelays[i] = @(arc4random_uniform(self.shineDuration / 2 * 100) / 100.0);
            //还剩下的时间
            CGFloat remain = self.shineDuration - [self.characterAnimationDelays[i] floatValue];
            //随机消失的时间
            self.characterAnimationDurations[i] = @(arc4random_uniform(remain * 100) / 100.0);
        }
        
    }
    //闪烁
    - (void)shine
    {
        [self shineWithCompletion:NULL];
    }
    
    //闪烁 以及 回调
    - (void)shineWithCompletion:(void (^)())completion
    {
    //    self.isShining 返回的是 self.displayLink.isPaused的相反值
        if (!self.isShining && self.isFadedOut) {
            self.completion = completion;
            // 每次闪烁开始,就把fadeout 设为 NO
            self.fadedOut = NO;
            
            [self startAnimationWithDuration:self.shineDuration];
        }
    }
    - (void)startAnimationWithDuration:(CFTimeInterval)duration
    {
        //开始的时间 ---》当前时间
        self.beginTime = CACurrentMediaTime();
        //结束的时间 ---》当前时间 + 2.5
        self.endTime = self.beginTime + self.shineDuration;
        //动画是否暂停 ---》NO ---》 动画开始
        self.displayLink.paused = NO;
        
    }
    
    //淡出
    - (void)fadeOut
    {
        [self fadeOutWithCompletion:NULL];
    
    }
    
    // 再点击屏幕后,(BOOL)isVisible 的返回值为 YES的时候调用
    // 初始化显示完全(更新字符状态)self.displayLink.isPaused --》YES
    - (void)fadeOutWithCompletion:(void (^)())completion
    {
            // 非闪烁 and 非动画时间
        if (!self.isShining &&!self.isFadedOut) {
            //执行BLOCK
            self.completion = completion;
            //淡出状态
            self.fadedOut = YES;
            //淡出时间 self.displayLink.isPaused --》NO --》动画开始
            [self startAnimationWithDuration:self.fadeoutDuration];
        }
    }
    
    - (BOOL)isShining
    {
        //没暂停的时候就闪烁
        return !self.displayLink.isPaused;
    }
    
    - (BOOL)isVisible
    {
        //没淡出的时候 就看得见
        return NO == self.isFadedOut;
    }
    //更新字符属性
    - (void)updateAttributedString
    {
        //到现在的时间
        CFTimeInterval now = CACurrentMediaTime();
        for (NSUInteger i = 0; i < self.attributedString.length; i ++) {
            //[NSCharacterSet whitespaceCharacterSet] 删除首尾空格
            //如果有当前字符 就返回YES
            if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[self.attributedString.string characterAtIndex:i]]) {
                // 从这里,结束 本次循环 ---》 跳出进入下次循环
                continue;
            }
            //指定字符范围执行block设置属性
            [self.attributedString enumerateAttribute:NSForegroundColorAttributeName inRange:NSMakeRange(i, 1) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
                //获得当前字符的透明度
                CGFloat currentAlpha = CGColorGetAlpha([(UIColor *)value CGColor]);
                //更新透明度
                //淡出并且透明度大于0||不在淡出透明度小于0||调用更新字符属性的时间-动画开始的时间>=当前字符的推迟时间
                BOOL shouldUpdateAlpha = (self.isFadedOut && currentAlpha > 0) || (!self.isFadedOut && currentAlpha < 1) || (now - self.beginTime) >= [self.characterAnimationDelays[i] floatValue];
                //如果没值 直接返回
                if (!shouldUpdateAlpha) {
                    return ;
                }
                //两副动画的间隔-推迟的时间/动画随机消失的时间
                // now现在的时间不短增加,所以percent会增大,以至于每个字体的颜色透明度增大
                // 大于1 作用等同于 1
                // 刷新这个 方法 用的link 一分钟60次刷新
                CGFloat percentage = (now - self.beginTime - [self.characterAnimationDelays[i] floatValue]) / ([self.characterAnimationDurations[i] floatValue]);
                //如果正在淡出
                if (self.isFadedOut) {
                    percentage = 1 - percentage;
                }
                //获取百分比的透明度
                UIColor *color = [self.textColor colorWithAlphaComponent:percentage];
                //将透明度赋值给当前字符
                [self.attributedString addAttribute:NSForegroundColorAttributeName value:color range:range];
            }];
        }
    //    set赋值
        [super setAttributedText:self.attributedString];
        //如果调用更新字符的时间大于动画结束的时间 动画暂停
        if (now > self.endTime){
            self.displayLink.paused = YES;
            //动画结束时候调用block
            if (self.completion) {
                self.completion();
            }
        }
    }
    - (NSMutableAttributedString *)initiaAttributedStringFromAttributedString:(NSAttributedString *)attributedString
    {
        //将传进来的自定义字符串转换为可自定义字符串
        NSMutableAttributedString *mutableAttributedString = [attributedString mutableCopy];
        //文字透明度为0
        UIColor *color = [self.textColor colorWithAlphaComponent:0];
        //将透明度和字符串的范围赋值给自定义
        [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:color range:NSMakeRange(0, mutableAttributedString.length)];
        
        return mutableAttributedString;
    }
    
    @end
    

    个人觉得加以修改用作第一次启动程序的动画还是挺炫酷的

    相关文章

      网友评论

      本文标题:解读RQShineLabel动画效果

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