美文网首页
iPhone经典解锁文字动画实现:FBShimmering

iPhone经典解锁文字动画实现:FBShimmering

作者: petyou | 来源:发表于2017-11-14 17:35 被阅读0次

FBShimmering是由Facebook出品的一个简单的框架,它实现了iPhone经典解锁的文字动画效果.导入头文件后使用看看:

- (void)viewDidLoad {

    [super viewDidLoad];

    FBShimmeringView *shimmeringView = [[FBShimmeringView alloc] initWithFrame:CGRectMake(0, 64,self.view.frame.size.width, 44)];

    shimmeringView.shimmering = YES;

    [self.view addSubview:shimmeringView];

    UILabel *contentLabel = [[UILabel alloc] initWithFrame:shimmeringView.bounds];

    contentLabel.text = @"scan or create two-dimension code";

    contentLabel.textColor =  [UIColor whiteColor];

    contentLabel.textAlignment = NSTextAlignmentCenter;

    contentLabel.backgroundColor = [UIColor redColor];

    shimmeringView.contentView = contentLabel;

}

效果图


可以看出使用起来非常简单.而FBShimmering的核心代码也只有500行左右.

主要思路:为要实现该效果的view.layer设置一个FBShimmeringLayer类型的mask属性,而这个mask上面添加了一个FBShimmeringMaskLayer类型的maskLayer.通过移动maskLayer使得view.layer处于不同opacity下,从而显示出全亮和蒙板这样交替的效果.

首先是FBShimmering.h声明了一个协议.FBShimmeringView遵循该协议,并生成相关属性的setter和getter方法

在FBShimmering.h协议中声明了如下属性:

@protocol FBShimmering

//!@abstract 是否开启动画,打开或者关闭

@property(nonatomic, assign, readwrite, getter = isShimmering) BOOL shimmering;

//!@abstract 两次动画之间的间隔 默认0.4s

@property(assign,nonatomic,readwrite) CFTimeInterval shimmeringPauseDuration;

//!@abstract 最终设置到了maskLayer的opacity上去了.越小蒙板效果越强

@property(assign, nonatomic, readwrite) CGFloat shimmeringOpacity;

//!@abstract 滑动的速度,默认230点/s

@property(assign, nonatomic, readwrite) CGFloat shimmeringSpeed;

//!@abstract 滑动动画开始前的缓冲动画时间

@property(assign, nonatomic, readwrite) CFTimeInterval shimmeringBeginFadeDuration;

//!@abstract 滑动动画结束后的缓冲动画时间

@property(assign, nonatomic, readwrite) CFTimeInterval shimmeringEndFadeDuration;

@abstract 绝对时间.滑动动画结束后的缓冲动画结束时间点

@property(assign, nonatomic, readonly) CFTimeInterval shimmeringFadeTime;

@end

在FBShimmeringView中通过遵循该协议并实现所有属性的setter和getter方法.它通过宏替换的方式予以实现.这种做法值得参考.

+ (Class)layerClass {

  return [FBShimmeringLayer class];

}

#define __layer ((FBShimmeringLayer *)self.layer)

#define LAYER_ACCESSOR(accessor, ctype) \

- (ctype)accessor { \

  return [__layer accessor]; \

}  //---------------------------------------  /*  getter 方法宏*/

#define LAYER_MUTATOR(mutator, ctype) \

- (void)mutator (ctype)value { \

  [__layer mutator value]; \

}   //---------------------------------------  /*  setter 方法宏*/

#define LAYER_RW_PROPERTY(accessor, mutator, ctype) \

  LAYER_ACCESSOR (accessor, ctype) \

  LAYER_MUTATOR (mutator, ctype) //---------------------------------------  /*  getter setter 整在一起*/

LAYER_RW_PROPERTY(isShimmering, setShimmering:, BOOL);

LAYER_RW_PROPERTY(shimmeringPauseDuration, setShimmeringPauseDuration:, CFTimeInterval);

LAYER_RW_PROPERTY(shimmeringOpacity, setShimmeringOpacity:, CGFloat);

LAYER_RW_PROPERTY(shimmeringSpeed, setShimmeringSpeed:, CGFloat);

LAYER_ACCESSOR(shimmeringFadeTime, CFTimeInterval);

LAYER_RW_PROPERTY(shimmeringBeginFadeDuration, setShimmeringBeginFadeDuration:, CFTimeInterval);

LAYER_RW_PROPERTY(shimmeringEndFadeDuration, setShimmeringEndFadeDuration:, CFTimeInterval);


在FBShimmeringLayer.m中,代码并不多.解读三个核心代码

- (void)_updateMaskColors {

  if (nil == _maskLayer) {

    return;

  }

 // 透明度从_shimmeringOpacity到1再到_shimmeringOpacity.即形成了一个连续的从半透明到不透明再到半透明的渐变.当滑动时,处于完全不透明的(alpha = 1)的地方相对的就是最亮的(只是没有被蒙起来),其它地方(alpha < 1)则有蒙板效果,相对不亮. 

  UIColor *maskedColor = [UIColor colorWithWhite:1.0 alpha:_shimmeringOpacity];

  UIColor *unmaskedColor = [UIColor whiteColor];

  // Create a gradient from masked to unmasked to masked.

  _maskLayer.colors = @[(__bridge id)maskedColor.CGColor, (__bridge id)unmaskedColor.CGColor, (__bridge id)maskedColor.CGColor];

}

- (void)_updateMaskLayout {

  // 因为超出mask layer的地方将被隐藏(mask特性),所以我们需要创建足够大的layer使之足以任何时候(在动画前后)都能将shimmered layer覆盖住

  CGFloat width = CGRectGetWidth(_contentLayer.bounds);

  if (0 == width) {

    return;

  }

   CGFloat extraDistance = width + _shimmeringSpeed * _shimmeringPauseDuration;

  // 一倍width是动画之前的盖住shimmered layer的.一倍width是从开始亮到结束亮,一倍width是结束动画盖住shimmered layer的.

  CGFloat fullShimmerLength = width * 3.0f + extraDistance;

  CGFloat travelDistance = width * 2.0f + extraDistance;

  _maskLayer.startPoint = CGPointMake((width + extraDistance) / fullShimmerLength, 0.0);

  _maskLayer.endPoint = CGPointMake(travelDistance / fullShimmerLength, 0.0);

  // position for the start of the animation

  _maskLayer.anchorPoint = CGPointZero;

  _maskLayer.position = CGPointMake(-travelDistance, 0.0);

  _maskLayer.bounds = CGRectMake(0.0, 0.0, fullShimmerLength, CGRectGetHeight(_contentLayer.bounds));

}

如下图:

流程图

创建动画的方法

- (void)_updateShimmering {

  [self _createMaskIfNeeded];

  if (!_shimmering && !_maskLayer) {

    return;

  }

  [self layoutIfNeeded];

  BOOL disableActions = [CATransaction disableActions];

  if (!_shimmering) {

    if (disableActions) {

      [self _clearMask];

    } else {

       //这个else分支指的是_shimmering属性从YES->NO.它不会立马移除正在执行的动画.而是将当前的执行完毕再停止

      CFTimeInterval slideEndTime = 0;

      CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];

      if (slideAnimation != nil) {

        CFTimeInterval now = CACurrentMediaTime();

        CFTimeInterval slideTotalDuration = now - slideAnimation.beginTime; // 已经执行的时间

        CFTimeInterval slideTimeOffset = fmod(slideTotalDuration, slideAnimation.duration);// 剩下的时间

       // 用剩下的时间生成一个finishAnimation并覆盖掉原来的animation

        CAAnimation *finishAnimation = shimmer_slide_finish(slideAnimation);

        finishAnimation.beginTime = now - slideTimeOffset;

        slideEndTime = finishAnimation.beginTime + slideAnimation.duration;

        [_maskLayer addAnimation:finishAnimation forKey:kFBShimmerSlideAnimationKey];

      }

    //  在滑动动画结束后添加淡出动画

      CABasicAnimation *fadeInAnimation = shimmer_end_fade_animation(self, _maskLayer.fadeLayer, 1.0, _shimmeringEndFadeDuration);

      fadeInAnimation.beginTime = slideEndTime;

      [_maskLayer.fadeLayer addAnimation:fadeInAnimation forKey:kFBFadeAnimationKey];

      _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 disableActions = [CATransaction disableActions];

      [CATransaction setDisableActions:YES];

      _maskLayer.fadeLayer.opacity = 0.0;

      [_maskLayer.fadeLayer removeAllAnimations];

      [CATransaction setDisableActions:disableActions];

    }

    // 添加滑动动画

    CAAnimation *slideAnimation = [_maskLayer animationForKey:kFBShimmerSlideAnimationKey];

    CFTimeInterval animationDuration = (CGRectGetWidth(_contentLayer.bounds) / _shimmeringSpeed) + _shimmeringPauseDuration;

    if (slideAnimation != nil) {

      [_maskLayer addAnimation:shimmer_slide_repeat(slideAnimation, animationDuration) forKey:kFBShimmerSlideAnimationKey];

    } else {

      slideAnimation = shimmer_slide_animation(self, animationDuration);

      slideAnimation.fillMode = kCAFillModeForwards;

      slideAnimation.removedOnCompletion = NO;

      slideAnimation.beginTime = CACurrentMediaTime() + fadeOutAnimation.duration;

      [_maskLayer addAnimation:slideAnimation forKey:kFBShimmerSlideAnimationKey];

    }

  }

}

附链接 Shimmer

相关文章

网友评论

      本文标题:iPhone经典解锁文字动画实现:FBShimmering

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