带辉光效果的跑马灯
效果
说明
并没有对代码进行封装,以后会在项目 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.h
//
// 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+GlowView.m
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.h
//
// 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
UIView+SetRect.m
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.h
//
// 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
NSString+LabelWidthAndHeight.m
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动画来实现重复移动的效果