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内容便全部展示出来。
网友评论