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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,471评论 6 30
  • Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Laye...
    小猫仔阅读 3,691评论 1 4
  • Core Animation Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,...
    45b645c5912e阅读 3,016评论 0 21
  • 原文 在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新。 1.UITableView的Group...
    无沣阅读 770评论 0 2
  • 大暑,连风都像热浪的天不会让人对花儿笑、鸟儿叫产生半点兴趣,她觉得来往的行人只顾着自己走路的频率,都沉浸在自己的精...
    话痨阅读 417评论 0 4