带辉光效果的跑马灯
效果
说明
并没有对代码进行封装,以后会在项目 Animation()里面进行集成,欢迎前去star。
源码
UIView+GlowView
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//// UIView+GlowView.h// GlowView//// Created by YouXianMing on 15/7/4.// Copyright (c) 2015年 YouXianMing. All rights reserved.//#import@interface UIView (GlowView)//// == 动画时间解析 ==//// 0.0 ------------- 0.0 ------------> glowOpacity [-------------] glowOpacity ------------> 0.0// T T T T// | | | |// | | | |// . . . .// hideDuration glowAnimationDuration glowDuration glowAnimationDuration//#pragma mark - 设置辉光效果/** * 辉光的颜色 */@property (nonatomic, strong) UIColor *glowColor;/** * 辉光的透明度 */@property (nonatomic, strong) NSNumber *glowOpacity;/** * 辉光的阴影半径 */@property (nonatomic, strong) NSNumber *glowRadius;#pragma mark - 设置辉光时间间隔/** * 一次完整的辉光周期(从显示到透明或者从透明到显示),默认1s */@property (nonatomic, strong) NSNumber *glowAnimationDuration;/** * 保持辉光时间(不设置,默认为0.5s) */@property (nonatomic, strong) NSNumber *glowDuration;/** * 不显示辉光的周期(不设置默认为0.5s) */@property (nonatomic, strong) NSNumber *hideDuration;#pragma mark - 辉光相关操作/** * 创建出辉光layer */- (void)createGlowLayer;/** * 插入辉光的layer */- (void)insertGlowLayer;/** * 移除辉光的layer */- (void)removeGlowLayer;/** * 显示辉光 */- (void)glowToshow;/** * 隐藏辉光 */- (void)glowToHide;/** * 开始循环辉光 */- (void)startGlowLoop;@end
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//// UIView+GlowView.m// GlowView//// Created by YouXianMing on 15/7/4.// Copyright (c) 2015年 YouXianMing. All rights reserved.//#import "UIView+GlowView.h"#import@interface UIView ()@property (nonatomic, strong) CALayer *glowLayer;@property (nonatomic, strong) dispatch_source_t dispatchSource;@end@implementation UIView (GlowView)- (void)createGlowLayer { UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale); [self.layer renderInContext:UIGraphicsGetCurrentContext()]; UIBezierPath* path = [UIBezierPath bezierPathWithRect:self.bounds]; [[self accessGlowColor] setFill]; [path fillWithBlendMode:kCGBlendModeSourceAtop alpha:1.0]; self.glowLayer = [CALayer layer]; self.glowLayer.frame = self.bounds; self.glowLayer.contents = (__bridge id)UIGraphicsGetImageFromCurrentImageContext().CGImage; self.glowLayer.opacity = 0.f; self.glowLayer.shadowOffset = CGSizeMake(0, 0); self.glowLayer.shadowOpacity = 1.f; UIGraphicsEndImageContext();}- (void)insertGlowLayer { if (self.glowLayer) { [self.layer addSublayer:self.glowLayer]; }}- (void)removeGlowLayer { if (self.glowLayer) { [self.glowLayer removeFromSuperlayer]; }}- (void)glowToshow { self.glowLayer.shadowColor = [self accessGlowColor].CGColor; self.glowLayer.shadowRadius = [self accessGlowRadius].floatValue; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; animation.fromValue = @(0.f); animation.toValue = [self accessGlowOpacity]; self.glowLayer.opacity = [self accessGlowOpacity].floatValue; animation.duration = [self accessAnimationDuration].floatValue; [self.glowLayer addAnimation:animation forKey:nil];}- (void)glowToHide { self.glowLayer.shadowColor = [self accessGlowColor].CGColor; self.glowLayer.shadowRadius = [self accessGlowRadius].floatValue; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; animation.fromValue = [self accessGlowOpacity]; animation.toValue = @(0.f); self.glowLayer.opacity = 0.f; animation.duration = [self accessAnimationDuration].floatValue; [self.glowLayer addAnimation:animation forKey:nil];}- (void)startGlowLoop { if (self.dispatchSource == nil) { CGFloat seconds = [self accessAnimationDuration].floatValue * 2 + [self accessGlowDuration].floatValue + [self accessHideDuration].floatValue; CGFloat delaySeconds = [self accessAnimationDuration].floatValue + [self accessGlowDuration].floatValue; self.dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(self.dispatchSource, dispatch_time(DISPATCH_TIME_NOW, 0), NSEC_PER_SEC * seconds, 0); dispatch_source_set_event_handler(self.dispatchSource, ^{ [self glowToshow]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * delaySeconds), dispatch_get_main_queue(), ^{ [self glowToHide]; }); }); dispatch_resume(self.dispatchSource); }}#pragma mark - 处理数据越界问题- (NSNumber *)accessGlowOpacity { if (self.glowOpacity) { if (self.glowOpacity.floatValue <= 0 || self.glowOpacity.floatValue > 1) { return @(0.8); } else { return self.glowOpacity; } } else { return @(0.8); }}- (NSNumber *)accessGlowDuration { if (self.glowDuration) { if (self.glowDuration.floatValue <= 0) { return @(0.5f); } else { return self.glowDuration; } } else { return @(0.5f); }}- (NSNumber *)accessHideDuration { if (self.hideDuration) { if (self.hideDuration.floatValue < 0) { return @(0.5); } else { return self.hideDuration; } } else { return @(0.5f); }}- (NSNumber *)accessAnimationDuration { if (self.glowAnimationDuration) { if (self.glowAnimationDuration.floatValue <= 0) { return @(1.f); } else { return self.glowAnimationDuration; } } else { return @(1.f); }}- (UIColor *)accessGlowColor { if (self.glowColor) { return self.glowColor; } else { return [UIColor redColor]; }}- (NSNumber *)accessGlowRadius { if (self.glowRadius) { if (self.glowRadius.floatValue <= 0) { return @(2.f); } else { return self.glowRadius; } } else { return @(2.f); }}#pragma mark - runtime属性NSString * const _recognizerDispatchSource = @"_recognizerDispatchSource";- (void)setDispatchSource:(dispatch_source_t)dispatchSource { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerDispatchSource), dispatchSource, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (dispatch_source_t)dispatchSource { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerDispatchSource));}NSString * const _recognizerGlowColor = @"_recognizerGlowColor";- (void)setGlowColor:(UIColor *)glowColor { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerGlowColor), glowColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (UIColor *)glowColor { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerGlowColor));}NSString * const _recognizerGlowOpacity = @"_recognizerGlowOpacity";- (void)setGlowOpacity:(NSNumber *)glowOpacity { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerGlowOpacity), glowOpacity, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSNumber *)glowOpacity { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerGlowOpacity));}NSString * const _recognizerGlowRadius = @"_recognizerGlowRadius";- (void)setGlowRadius:(NSNumber *)glowRadius { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerGlowRadius), glowRadius, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSNumber *)glowRadius { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerGlowRadius));}NSString * const _recognizerGlowAnimationDuration = @"_recognizerGlowAnimationDuration";- (void)setGlowAnimationDuration:(NSNumber *)glowAnimationDuration { objc_setAssociatedObject(self, (__bridge const void *)(glowAnimationDuration), _recognizerGlowAnimationDuration, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSNumber *)glowAnimationDuration { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerGlowAnimationDuration));}NSString * const _recognizerGlowDuration = @"_recognizerGlowDuration";- (void)setGlowDuration:(NSNumber *)glowDuration { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerGlowDuration), glowDuration, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSNumber *)glowDuration { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerGlowDuration));}NSString * const _recognizerHideDuration = @"_recognizerHideDuration";- (void)setHideDuration:(NSNumber *)hideDuration { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerHideDuration), hideDuration, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSNumber *)hideDuration { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerHideDuration));}NSString * const _recognizerGlowLayer = @"_recognizerGlowLayer";- (void)setGlowLayer:(CALayer *)glowLayer { objc_setAssociatedObject(self, (__bridge const void *)(_recognizerGlowLayer), glowLayer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (CALayer *)glowLayer { return objc_getAssociatedObject(self, (__bridge const void *)(_recognizerGlowLayer));}@end
UIView+SetRect
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//// UIView+SetRect.h// UIView//// Created by YouXianMing on 16/1/29.// Copyright © 2016年 YouXianMing. All rights reserved.//#import/** * UIScreen width. */#define Width [UIScreen mainScreen].bounds.size.width/** * UIScreen height. */#define Height [UIScreen mainScreen].bounds.size.height/** * Status bar height. */#define StatusBarHeight 20.f/** * Navigation bar height. */#define NavigationBarHeight 44.f/** * Tabbar height. */#define TabbarHeight 49.f/** * Status bar & navigation bar height. */#define StatusBarAndNavigationBarHeight (20.f + 44.f)/** * iPhone4 or iPhone4s */#define iPhone4_4s (Width == 320.f && Height == 480.f ? YES : NO)/** * iPhone5 or iPhone5s */#define iPhone5_5s (Width == 320.f && Height == 568.f ? YES : NO)/** * iPhone6 or iPhone6s */#define iPhone6_6s (Width == 375.f && Height == 667.f ? YES : NO)/** * iPhone6Plus or iPhone6sPlus */#define iPhone6_6sPlus (Width == 414.f && Height == 736.f ? YES : NO)@interface UIView (SetRect)/*---------------------- * Absolute coordinate * ----------------------*/@property (nonatomic) CGPoint viewOrigin;@property (nonatomic) CGSize viewSize;@property (nonatomic) CGFloat x;@property (nonatomic) CGFloat y;@property (nonatomic) CGFloat width;@property (nonatomic) CGFloat height;@property (nonatomic) CGFloat top;@property (nonatomic) CGFloat bottom;@property (nonatomic) CGFloat left;@property (nonatomic) CGFloat right;@property (nonatomic) CGFloat centerX;@property (nonatomic) CGFloat centerY;/*---------------------- * Relative coordinate * ----------------------*/@property (nonatomic, readonly) CGFloat middleX;@property (nonatomic, readonly) CGFloat middleY;@property (nonatomic, readonly) CGPoint middlePoint;@end
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//// UIView+SetRect.m// UIView//// Created by YouXianMing on 16/1/29.// Copyright © 2016年 YouXianMing. All rights reserved.//#import "UIView+SetRect.h"@implementation UIView (SetRect)- (CGPoint)viewOrigin { return self.frame.origin;}- (void)setViewOrigin:(CGPoint)viewOrigin { CGRect newFrame = self.frame; newFrame.origin = viewOrigin; self.frame = newFrame;}- (CGSize)viewSize { return self.frame.size;}- (void)setViewSize:(CGSize)viewSize { CGRect newFrame = self.frame; newFrame.size = viewSize; self.frame = newFrame;}- (CGFloat)x { return self.frame.origin.x;}- (void)setX:(CGFloat)x { CGRect newFrame = self.frame; newFrame.origin.x = x; self.frame = newFrame;}- (CGFloat)y { return self.frame.origin.y;}- (void)setY:(CGFloat)y { CGRect newFrame = self.frame; newFrame.origin.y = y; self.frame = newFrame;}- (CGFloat)width { return CGRectGetWidth(self.bounds);}- (void)setWidth:(CGFloat)width { CGRect newFrame = self.frame; newFrame.size.width = width; self.frame = newFrame;}- (CGFloat)height { return CGRectGetHeight(self.bounds);}- (void)setHeight:(CGFloat)height { CGRect newFrame = self.frame; newFrame.size.height = height; self.frame = newFrame;}- (CGFloat)top { return self.frame.origin.y;}- (void)setTop:(CGFloat)top { CGRect newFrame = self.frame; newFrame.origin.y = top; self.frame = newFrame;}- (CGFloat)bottom { return self.frame.origin.y + self.frame.size.height;}- (void)setBottom:(CGFloat)bottom { CGRect newFrame = self.frame; newFrame.origin.y = bottom - self.frame.size.height; self.frame = newFrame;}- (CGFloat)left { return self.frame.origin.x;}- (void)setLeft:(CGFloat)left { CGRect newFrame = self.frame; newFrame.origin.x = left; self.frame = newFrame;}- (CGFloat)right { return self.frame.origin.x + self.frame.size.width;}- (void)setRight:(CGFloat)right { CGRect newFrame = self.frame; newFrame.origin.x = right - self.frame.size.width; self.frame = newFrame;}- (CGFloat)centerX { return self.center.x;}- (void)setCenterX:(CGFloat)centerX { CGPoint newCenter = self.center; newCenter.x = centerX; self.center = newCenter;}- (CGFloat)centerY { return self.center.y;}- (void)setCenterY:(CGFloat)centerY { CGPoint newCenter = self.center; newCenter.y = centerY; self.center = newCenter;}- (CGFloat)middleX { return CGRectGetWidth(self.bounds) / 2.f;}- (CGFloat)middleY { return CGRectGetHeight(self.bounds) / 2.f;}- (CGPoint)middlePoint { return CGPointMake(CGRectGetWidth(self.bounds) / 2.f, CGRectGetHeight(self.bounds) / 2.f);}@end
NSString+LabelWidthAndHeight
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//// NSString+LabelWidthAndHeight.h// ZiPeiYi//// Created by YouXianMing on 15/12/9.// Copyright © 2015年 YouXianMing. All rights reserved.//#import#import @interface NSString (LabelWidthAndHeight)/** * Get the string's height with the fixed width. * * @param attribute String's attribute, eg. attribute = @{NSFontAttributeName: [UIFont systemFontOfSize:18.f]} * @param width Fixed width. * * @return String's height. */- (CGFloat)heightWithStringAttribute:(NSDictionary *)attribute fixedWidth:(CGFloat)width;/** * Get the string's width. * * @param attribute String's attribute, eg. attribute = @{NSFontAttributeName: [UIFont systemFontOfSize:18.f]} * * @return String's width. */- (CGFloat)widthWithStringAttribute:(NSDictionary *)attribute;/** * Get a line of text height. * * @param attribute String's attribute, eg. attribute = @{NSFontAttributeName: [UIFont systemFontOfSize:18.f]} * * @return String's width. */+ (CGFloat)aLineOfTextHeightWithStringAttribute:(NSDictionary *)attribute;@end
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//// NSString+LabelWidthAndHeight.m// ZiPeiYi//// Created by YouXianMing on 15/12/9.// Copyright © 2015年 YouXianMing. All rights reserved.//#import "NSString+LabelWidthAndHeight.h"@implementation NSString (LabelWidthAndHeight)- (CGFloat)heightWithStringAttribute:(NSDictionary*)attribute fixedWidth:(CGFloat)width { NSParameterAssert(attribute); CGFloat height = 0; if (self.length) { CGRect rect = [self boundingRectWithSize:CGSizeMake(width, MAXFLOAT) options:NSStringDrawingTruncatesLastVisibleLine |NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attribute context:nil]; height = rect.size.height; } return height;}- (CGFloat)widthWithStringAttribute:(NSDictionary *)attribute { NSParameterAssert(attribute); CGFloat width = 0; if (self.length) { CGRect rect = [self boundingRectWithSize:CGSizeMake(MAXFLOAT, 0) options:NSStringDrawingTruncatesLastVisibleLine |NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attribute context:nil]; width = rect.size.width; } return width;}+ (CGFloat)aLineOfTextHeightWithStringAttribute:(NSDictionary *)attribute { CGFloat height = 0; CGRect rect = [@"One" boundingRectWithSize:CGSizeMake(200, MAXFLOAT) options:NSStringDrawingTruncatesLastVisibleLine |NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attribute context:nil]; height = rect.size.height; return height;}@end
ViewController.m
//// ViewController.m// UILabel//// Created by YouXianMing on 16/4/13.// Copyright © 2016年 YouXianMing. All rights reserved.//#import "ViewController.h"#import "UIView+SetRect.h"#import "UIView+GlowView.h"#import "NSString+LabelWidthAndHeight.h"@interface ViewController ()@property (nonatomic, strong) UIView *contentView;@property (nonatomic, strong) UILabel *label;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; self.contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 250.f, 20)]; self.contentView.layer.borderWidth = 0.5f; self.contentView.layer.masksToBounds = YES; self.contentView.layer.borderColor = [[UIColor grayColor] colorWithAlphaComponent:0.25f].CGColor; self.contentView.center = self.view.center; [self.view addSubview:self.contentView]; NSString *string = @"Copyright © 2016 YouXianMing. All rights reserved."; CGFloat width = [string widthWithStringAttribute:@{NSFontAttributeName : [UIFont fontWithName:@"Heiti SC" size:14.f]}]; self.label = [[UILabel alloc] initWithFrame:CGRectMake(self.contentView.width, 0, width, self.contentView.height)]; self.label.font = [UIFont fontWithName:@"Heiti SC" size:14.f]; self.label.text = string; [self.contentView addSubview:self.label]; [self doAnimation]; // Start glow. self.label.glowRadius = @(1.f); self.label.glowOpacity = @(1.f); self.label.glowColor = [[UIColor cyanColor] colorWithAlphaComponent:0.5f]; self.label.glowDuration = @(1.f); self.label.hideDuration = @(3.f); self.label.glowAnimationDuration = @(2.f); [self.label createGlowLayer]; [self.label insertGlowLayer]; [self.label startGlowLoop]; UIPanGestureRecognizer *tapGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureEvent:)]; tapGesture.delegate = self; [self.contentView addGestureRecognizer:tapGesture];}- (void)doAnimation { CGPoint fromPoint = CGPointMake(self.contentView.width + self.label.width / 2.f, self.contentView.height / 2.f); UIBezierPath *movePath = [UIBezierPath bezierPath]; [movePath moveToPoint:fromPoint]; [movePath addLineToPoint:CGPointMake(-self.label.width / 2, self.contentView.height / 2.f)]; CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; moveAnimation.path = movePath.CGPath; moveAnimation.removedOnCompletion = YES; moveAnimation.duration = 8.f; moveAnimation.delegate = self; [self.label.layer addAnimation:moveAnimation forKey:nil];}- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if (flag) { [self doAnimation]; }}- (void)pauseLayer:(CALayer*)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime;}- (void)resumeLayer:(CALayer*)layer { CFTimeInterval pausedTime = layer.timeOffset; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause;}- (void)tapGestureEvent:(UIPanGestureRecognizer *)tapGesture { if (tapGesture.state == UIGestureRecognizerStateBegan) { NSLog(@"拖拽"); [self pauseLayer:self.label.layer]; } else if (tapGesture.state == UIGestureRecognizerStateEnded) { NSLog(@"释放"); [self resumeLayer:self.label.layer]; }}@end
细节
暂停CALayer的动画以及恢复CALayer的动画
用了贝塞尔曲线的Path动画来实现重复移动的效果