美文网首页
IOS - Shimmer

IOS - Shimmer

作者: 我有小尾巴快看 | 来源:发表于2017-11-14 17:29 被阅读74次

    Shimmer是一款开源的加载效果工具,能够非常简单地向应用中的任何视图添加闪闪发光的字体效果,并且不会显得突兀。Shimmer最初是Facebook推出的Paper在开发过程中所使用到的工具,后被Facebook基于BSD许可协议开源,支持iOS 6及其以上系统。
    参考了IOS之Shimmer的完整解析的内容。

    简单使用

    @interface ViewController1 ()
    @property (nonatomic,strong) FBShimmeringView *shimmeringView;
    @end
    
    @implementation ViewController1
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        self.view.backgroundColor = [UIColor blackColor];
        
        _shimmeringView = [[FBShimmeringView alloc]init];
        _shimmeringView.shimmering = YES;
        _shimmeringView.shimmeringBeginFadeDuration = 0.3;
        _shimmeringView.shimmeringOpacity = 0.3;
        [self.view addSubview:_shimmeringView];
        
        UILabel *label = [[UILabel alloc]initWithFrame:_shimmeringView.bounds];
        label.text = @"Shimmer";
        label.textAlignment = NSTextAlignmentCenter;
        label.font = [UIFont systemFontOfSize:60];
        label.textColor = [UIColor whiteColor];
        
        _shimmeringView.contentView = label;
    }
    
    - (void)viewWillLayoutSubviews {
        [super viewWillLayoutSubviews];
        self.shimmeringView.frame = CGRectMake(0,20,[UIScreen mainScreen].bounds.size.width,100);
    }
    
    效果图

    源码解析

    Shimmer有三个文件,分别是FBShimmering,FBShimmeringLayer ,FBShimmeringView。

    • FBShimmering: 协议,封装一些常用的属性,比如:速度、透明度、持续时间等。
    • FBShimmeringLayer : 实现了FBShimmering协议,是实现整个动画效果的核心部分。
    • FBShimmeringView : 实现FBShimmering协议,主要用来修改其图层类为FBShimmeringLayer,提供类似代理功能,并对外提供View。
    FBShimmering
    typedef NS_ENUM(NSInteger, FBShimmerDirection) {
        FBShimmerDirectionRight,    // Shimmer animation goes from left to right
        FBShimmerDirectionLeft,     // Shimmer animation goes from right to left
        FBShimmerDirectionUp,       // Shimmer animation goes from below to above
        FBShimmerDirectionDown,     // Shimmer animation goes from above to below
    };
    
    @protocol FBShimmering <NSObject>
    
    @property (nonatomic, assign, readwrite, getter = isShimmering) BOOL shimmering;
    //YES时闪光,NO则停止,默认为NO。
    @property (assign, nonatomic, readwrite) CFTimeInterval shimmeringPauseDuration;
    //两次闪光的间隔,默认0.4秒
    @property (assign, nonatomic, readwrite) CGFloat shimmeringAnimationOpacity;
    //当闪烁时内容的不透明性。默认值为1。
    @property (assign, nonatomic, readwrite) CGFloat shimmeringOpacity;
    //在闪烁之前内容的不透明度。 默认为0.5。
    @property (assign, nonatomic, readwrite) CGFloat shimmeringSpeed;
    //闪烁的速度,以每秒点数。 默认为230。
    @property (assign, nonatomic, readwrite) CGFloat shimmeringHighlightLength;
    //闪烁的高光长度。 范围[0,1],默认为0.33。
    @property (assign, nonatomic, readwrite, getter = shimmeringHighlightLength, setter = setShimmeringHighlightLength:) CGFloat shimmeringHighlightWidth;
    //就像“闪烁高光长度”一样,只是为了向下兼容
    @property (assign, nonatomic, readwrite) FBShimmerDirection shimmeringDirection;
    //闪闪发光的动画的方向。 默认为FBShimmerDirectionRight。
    @property (assign, nonatomic, readwrite) CFTimeInterval shimmeringBeginFadeDuration;
    //闪烁开始时使用的淡出时间。 默认为0.1。
    @property (assign, nonatomic, readwrite) CFTimeInterval shimmeringEndFadeDuration;
    //闪光结束时使用的淡入淡出时间。 默认为0.3。
    @property (assign, nonatomic, readonly) CFTimeInterval shimmeringFadeTime;
     //绝对CoreAnimation媒体的时候,淡光会淡入。仅在将{@ref闪烁}设置为NO后才有效。
    
    FBShimmeringView
    + (Class)layerClass {
      return [FBShimmeringLayer class];
    }
    

    这里重写了layerClass方法,将FBShimmeringView的图层修改为FBShimmeringLayer。

    #define __layer ((FBShimmeringLayer *)self.layer)
    
    #define LAYER_ACCESSOR(accessor, ctype) \
    - (ctype)accessor { \
      return [__layer accessor]; \
    }
    
    #define LAYER_MUTATOR(mutator, ctype) \
    - (void)mutator (ctype)value { \
      [__layer mutator value]; \
    }
    
    #define LAYER_RW_PROPERTY(accessor, mutator, ctype) \
      LAYER_ACCESSOR (accessor, ctype) \
      LAYER_MUTATOR (mutator, ctype)
    
    LAYER_RW_PROPERTY(isShimmering, setShimmering:, BOOL)
    LAYER_RW_PROPERTY(shimmeringPauseDuration, setShimmeringPauseDuration:, CFTimeInterval)
    LAYER_RW_PROPERTY(shimmeringAnimationOpacity, setShimmeringAnimationOpacity:, CGFloat)
    LAYER_RW_PROPERTY(shimmeringOpacity, setShimmeringOpacity:, CGFloat)
    LAYER_RW_PROPERTY(shimmeringSpeed, setShimmeringSpeed:, CGFloat)
    LAYER_RW_PROPERTY(shimmeringHighlightLength, setShimmeringHighlightLength:, CGFloat)
    LAYER_RW_PROPERTY(shimmeringDirection, setShimmeringDirection:, FBShimmerDirection)
    LAYER_ACCESSOR(shimmeringFadeTime, CFTimeInterval)
    LAYER_RW_PROPERTY(shimmeringBeginFadeDuration, setShimmeringBeginFadeDuration:, CFTimeInterval)
    LAYER_RW_PROPERTY(shimmeringEndFadeDuration, setShimmeringEndFadeDuration:, CFTimeInterval)
    

    这里通过宏定义的方式实现了协议里的属性以及get和set方法。

    - (void)setContentView:(UIView *)contentView {
      if (contentView != _contentView) {
        _contentView = contentView;
        [self addSubview:contentView];
        __layer.contentLayer = contentView.layer;
      }
    }
    

    这里设置了contentView。

    FBShimmeringLayer
    #if TARGET_IPHONE_SIMULATOR
    UIKIT_EXTERN float UIAnimationDragCoefficient(void); // UIKit私有方法
    #endif
    
    static CGFloat FBShimmeringLayerDragCoefficient(void)
    {
    #if TARGET_IPHONE_SIMULATOR
      return UIAnimationDragCoefficient();
    #else
      return 1.0;
    #endif
    }
    

    通过UIKIT_EXTERN的方式将UIKit的私有方法UIAnimationDragCoefficient引入进来。该方法返回一个"拖拽系数",正常情况下这个值为1。同时这个方法对应模拟器的"Slow Animations"选项,当我们打开这个选项的时候, 它的值变成了10,也就是说执行的动画比原来慢了10倍。

    首先初始化FBShimmeringLayer会设置一些默认参数

    - (instancetype)init {
      if (self = [super init]) {
        _shimmeringPauseDuration = 0.4;
        _shimmeringSpeed = 230.0;
        _shimmeringHighlightLength = 1.0;
        _shimmeringAnimationOpacity = 0.5;
        _shimmeringOpacity = 1.0;
        _shimmeringDirection = FBShimmerDirectionRight;
        _shimmeringBeginFadeDuration = 0.1;
        _shimmeringEndFadeDuration = 0.3;
      }
      return self;
    }
    

    如果想要闪烁需要在初始化后将shimmering属性设置为YES,这时会触发setShimmering方法。

    - (void)setShimmering:(BOOL)shimmering {
      if (shimmering != _shimmering) {
        _shimmering = shimmering;
        [self _updateShimmering];
      }
    }
    

    FBShimmeringLayer所有的属性的set方法都会触发更新闪烁效果的方法_updateShimmering。这个方法也是最核心的部分。整个过程分为两个步骤:
    1:暂停闪动的时候伴随一个淡入的动画。
    2:开始闪动的时候一个淡出的动画、接着跟随不断闪动的动画。

    - (void)_updateShimmering {
      [self _createMaskIfNeeded];//创建_maskLayer
      if (!_shimmering && !_maskLayer) return; // 如果不允许闪烁并且_maskLayer不存在时直接返回
      [self layoutIfNeeded];  // 立刻重新布局
    
      BOOL disableActions = [CATransaction disableActions];
      if (!_shimmering) {//先判断是否禁用图层行为
        if (disableActions) {
          [self _clearMask];//禁用了就移除蒙版
        } else {
          CFTimeInterval slideEndTime = 0;
          CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];// 没有禁用则kvc获取闪烁动画
          if (slideAnimation) {//判断闪烁动画是否为空
            // 获取动画已运行时间
            CFTimeInterval now = CACurrentMediaTime();
            CFTimeInterval slideTotalDuration = now - slideAnimation.beginTime;
    
            // 通过动画运行总的时间和每次运行的时间取模,得到当前闪烁的时间偏移,即当前动画在自己的一次循环内已运行的时间。
            CFTimeInterval slideTimeOffset = fmod(slideTotalDuration, slideAnimation.duration);
    
            //通过这个时间偏移计算出这次闪烁结束时的时间
            CAAnimation *finishAnimation = shimmer_slide_finish(slideAnimation);
    
            // 这个时间用来构造淡入动画的beginTime
            finishAnimation.beginTime = now - slideTimeOffset;
    
            // 设置结束时间为beginTime加上一次动画的时间
            slideEndTime = finishAnimation.beginTime + slideAnimation.duration;
           //最后将这个淡入动画加入这个图层
            [_maskLayer addAnimation:finishAnimation forKey:kFBShimmerSlideAnimationKey];
          }
    
          // fade in text at slideEndTime
          CABasicAnimation *fadeInAnimation = shimmer_end_fade_animation(self, _maskLayer.fadeLayer, 1.0, _shimmeringEndFadeDuration);
          fadeInAnimation.beginTime = slideEndTime;
          [_maskLayer.fadeLayer addAnimation:fadeInAnimation forKey:kFBFadeAnimationKey];
    
          // expose end time for synchronization
          _shimmeringFadeTime = slideEndTime;
        }
      } else {
        CABasicAnimation *fadeOutAnimation = nil;
        if (_shimmeringBeginFadeDuration > 0.0 && !disableActions) {//判断是否支持淡出动画
          //支持就构建一个淡出动画并加入图层
          fadeOutAnimation = shimmer_begin_fade_animation(self, _maskLayer.fadeLayer, 0.0, _shimmeringBeginFadeDuration);
          [_maskLayer.fadeLayer addAnimation:fadeOutAnimation forKey:kFBFadeAnimationKey];
        } else {
          BOOL innerDisableActions = [CATransaction disableActions];
          [CATransaction setDisableActions:YES];
          //如果没有就设置透明属性为0,并移除所有动画。
          _maskLayer.fadeLayer.opacity = 0.0;
          [_maskLayer.fadeLayer removeAllAnimations];
          
          [CATransaction setDisableActions:innerDisableActions];
        }
    
        //   然后通过KVC获取闪动动画
        CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];
        
        // compute shimmer duration
        CGFloat length = 0.0f;
        if (_shimmeringDirection == FBShimmerDirectionDown ||
            _shimmeringDirection == FBShimmerDirectionUp) {
          length = CGRectGetHeight(_contentLayer.bounds);
        } else {
          length = CGRectGetWidth(_contentLayer.bounds);
        }
        CFTimeInterval animationDuration = (length / _shimmeringSpeed) + _shimmeringPauseDuration;
        
        if (slideAnimation) {//,并判断是否为空
          // 不为空则调用shimmer_slide_repeat方法主要将其repeatCount属性设置为没有限制。
          [_maskLayer addAnimation:shimmer_slide_repeat(slideAnimation, animationDuration, _shimmeringDirection) forKey:kFBShimmerSlideAnimationKey];
        } else {
          //为空则初始化一个闪动动画。最后将其加入到图层中。
          slideAnimation = shimmer_slide_animation(self, animationDuration, _shimmeringDirection);
          slideAnimation.fillMode = kCAFillModeForwards;
          slideAnimation.removedOnCompletion = NO;
          slideAnimation.beginTime = CACurrentMediaTime() + fadeOutAnimation.duration;
          [_maskLayer addAnimation:slideAnimation forKey:kFBShimmerSlideAnimationKey];
        }
      }
    }
    

    _createMaskIfNeeded用来创建蒙版

    - (void)_createMaskIfNeeded
    {
      if (_shimmering && !_maskLayer) {
        _maskLayer = [FBShimmeringMaskLayer layer];
        _maskLayer.delegate = self;
        _contentLayer.mask = _maskLayer;
        [self _updateMaskColors];
        [self _updateMaskLayout];
      }
    }
    

    _updateMaskColors为蒙版创建颜色数组,这里需要注意的是蒙版所对应的 CALayer所能表现出来的属性只能是透明度。纵使蒙版是其他颜色或是图片,也不能表现出来想要的色彩效果。所以换成其他的颜色数组效果也是一样的。

    - (void)_updateMaskColors
    {
      if (nil == _maskLayer) {
        return;
      }
    
      // We create a gradient to be used as a mask.
      // In a mask, the colors do not matter, it's the alpha that decides the degree of masking.
      UIColor *maskedColor = [UIColor colorWithWhite:1.0 alpha:_shimmeringOpacity];
      UIColor *unmaskedColor = [UIColor colorWithWhite:1.0 alpha:_shimmeringAnimationOpacity];
    
      // Create a gradient from masked to unmasked to masked.
      _maskLayer.colors = @[(__bridge id)maskedColor.CGColor, (__bridge id)unmaskedColor.CGColor, (__bridge id)maskedColor.CGColor];
    }
    

    _updateMaskLayout主要用来初始化蒙版。先根据闪烁的方向计算出长度,如果长度为0直接返回。在根据长度分别计算出蒙版的长度、蒙版移动的距离。在根据蒙版的长度和移动距离计算出蒙版图层的startPoint以及endPoint。这里将anchorPoint设置为(0.0),主要是方便图层的position计算。

    - (void)_updateMaskLayout
    {
      // Everything outside the mask layer is hidden, so we need to create a mask long enough for the shimmered layer to be always covered by the mask.
      CGFloat length = 0.0f;
      if (_shimmeringDirection == FBShimmerDirectionDown ||
        _shimmeringDirection == FBShimmerDirectionUp) {
        length = CGRectGetHeight(_contentLayer.bounds);
      } else {
        length = CGRectGetWidth(_contentLayer.bounds);
      }
      if (0 == length) {
        return;
      }
    
      // extra distance for the gradient to travel during the pause.
      CGFloat extraDistance = length + _shimmeringSpeed * _shimmeringPauseDuration;
    
      // compute how far the shimmering goes
      CGFloat fullShimmerLength = length * 3.0f + extraDistance;
      CGFloat travelDistance = length * 2.0f + extraDistance;
      
      // position the gradient for the desired width
      CGFloat highlightOutsideLength = (1.0 - _shimmeringHighlightLength) / 2.0;
      _maskLayer.locations = @[@(highlightOutsideLength),
                               @(0.5),
                               @(1.0 - highlightOutsideLength)];
    
      CGFloat startPoint = (length + extraDistance) / fullShimmerLength;
      CGFloat endPoint = travelDistance / fullShimmerLength;
      
      // position for the start of the animation
      _maskLayer.anchorPoint = CGPointZero;
      if (_shimmeringDirection == FBShimmerDirectionDown ||
          _shimmeringDirection == FBShimmerDirectionUp) {
        _maskLayer.startPoint = CGPointMake(0.0, startPoint);
        _maskLayer.endPoint = CGPointMake(0.0, endPoint);
        _maskLayer.position = CGPointMake(0.0, -travelDistance);
        _maskLayer.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(_contentLayer.bounds), fullShimmerLength);
      } else {
        _maskLayer.startPoint = CGPointMake(startPoint, 0.0);
        _maskLayer.endPoint = CGPointMake(endPoint, 0.0);
        _maskLayer.position = CGPointMake(-travelDistance, 0.0);
        _maskLayer.bounds = CGRectMake(0.0, 0.0, fullShimmerLength, CGRectGetHeight(_contentLayer.bounds));
      }
    }
    

    _clearMask清除蒙版,但是在这之间需要禁用图层行为,所以先保存之前的状态,禁用后清除蒙版,然后恢复图层行为。

    - (void)_clearMask
    {
      if (nil == _maskLayer) {
        return;
      }
    
      BOOL disableActions = [CATransaction disableActions];
      [CATransaction setDisableActions:YES];
    
      self.maskLayer = nil;
      _contentLayer.mask = nil;
      
      [CATransaction setDisableActions:disableActions];
    }
    

    蒙版的作用是利用Alpha通道使View的内容有部分展示功能,我们能通过mask去给内容的特定区域增加特定的效果。公式如下:

    展示的alpha = content.alpha * maskLayer.alpah

    很明白,如果maskLayer.alpha=0,那么相对应的content部分内容就会被遮挡。如果maskLayer.alpha=1,那么content内容便全部展示出来。

    相关文章

      网友评论

          本文标题:IOS - Shimmer

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