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