带辉光效果的跑马灯
效果
说明
并没有对代码进行封装,以后会在项目 Animation(https://github.com/YouXianMing/Animations)里面进行集成,欢迎前去star。
源码
UIView+GlowView
// // UIView+GlowView.h // GlowView // // Created by YouXianMing on 15/7/4. // Copyright (c) 2015年 YouXianMing. All rights reserved. // #import <UIKit/UIKit.h> @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
// // UIView+GlowView.m // GlowView // // Created by YouXianMing on 15/7/4. // Copyright (c) 2015年 YouXianMing. All rights reserved. // #import "UIView+GlowView.h" #import <objc/runtime.h> @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
// // UIView+SetRect.h // UIView // // Created by YouXianMing on 16/1/29. // Copyright © 2016年 YouXianMing. All rights reserved. // #import <UIKit/UIKit.h> /** * 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
// // 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
// // NSString+LabelWidthAndHeight.h // ZiPeiYi // // Created by YouXianMing on 15/12/9. // Copyright © 2015年 YouXianMing. All rights reserved. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @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 <NSString *, id> *)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 <NSString *, id> *)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 <NSString *, id> *)attribute; @end
// // 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 <NSString *, id> *)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 <NSString *, id> *)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 <NSString *, id> *)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 () <UIGestureRecognizerDelegate> @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动画来实现重复移动的效果