介绍
因业务需求,需要做一个跑马灯效果的公告栏,查了网上大部分制作原理,都不能满足也无需求,决定自己造轮子
效果
需求
- 如果文字太多,跑马灯效果;否则,无动画中间位置显示
- 默认跑马灯10秒结束,后自动隐藏;短文一样10秒显隐
- 随时更换内容,更换内容是时间重新计算
- 点击关闭按钮,消失
原理
CoreAnimation
动画实现,通过检测动画的开始与结束,进行业务处理
代码部分
.h文件
@class MarqueeView;
typedef NS_ENUM(NSInteger, MarqueeViewDirection) {
MarqueeDirectionLeft,// 从右向左
MarqueeDirectionRight // 从左向右
};
@protocol MarqueeViewDelegate
@optional
// 动画结束回调
- (void)marqueeView:(MarqueeView *)marqueeView animationDidStopFinished:(BOOL)fnished;
// 关闭
- (void)closeMarqueeView:(MarqueeView *)marqueeView;
@end
@interface MarqueeView : UIView
/// 代理
@property (nonatomic, weak) id delegate;
/// 速度 default 1.0
@property (nonatomic, assign) CGFloat speed;
/// 动画时间 设置完后speed失去作用
@property (nonatomic, assign) CGFloat duration;
/// 方向 default MarqueeDirectionLeft
@property (nonatomic, assign) MarqueeViewDirection marqueeDirection;
/// 是否需要标题, 需要在添加视图之前设置 default NO
@property (nonatomic, assign) BOOL isNeedTitle;
- (instancetype)init NS_UNAVAILABLE;
/**
添加内容视图
@param view 内容视图
*/
- (void)addContentView:(UIView *)view;
/**
开始动画
*/
- (void)startAnimation;
/**
结束动画
*/
- (void)stopAnimation;
/**
暂停动画
*/
- (void)pauseAnimation;
/**
重新开始动画
*/
- (void)resumeAnimation;
.m文件
static CGFloat const kTitleWidth = 80;
static CGFloat const kCancleWidth = 30;
static CGFloat const kDefaultDuration = 10;
@interface MarqueeView () {
/// 整体控件宽度
CGFloat _width;
/// 整体控件高度
CGFloat _height;
/// 动画视图的宽度
CGFloat _animationViewWidth;
/// 动画视图的高度
CGFloat _animationViewHeight;
/// 内容视图
UIView *_contentView;
UIView *_bgView;
}
/// 背景视图
@property (nonatomic, strong) UIView *bgView;
/// 动画视图
@property (nonatomic, strong) UIView *animationView;
/// title视图
@property (nonatomic, strong) UIButton *titleBtn;
/// 关闭按钮
@property (nonatomic, strong) UIButton *closeBtn;
/// 是否重新赋值开始动画
@property (nonatomic, assign) BOOL isRestart;
@end
@implementation MarqueeView
#pragma mark ================== init method ==================
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_width = frame.size.width;
_height = frame.size.height;
self.layer.cornerRadius = _height / 2;
self.layer.masksToBounds = YES;
_isRestart = YES;
_speed = 1.f;
_marqueeDirection = MarqueeDirectionLeft;
[self addSubview:self.bgView];
[_bgView addSubview:self.animationView];
[self addSubview:self.titleBtn];
[self addSubview:self.closeBtn];
}
return self;
}
#pragma mark ================== public method ==================
- (void)addContentView:(UIView *)view {
[_contentView removeFromSuperview];
view.frame = view.bounds;
_contentView = view;
if (!_isNeedTitle) {
self.animationView.frame = view.bounds;
self.layer.cornerRadius = 0;
} else {
_titleBtn.frame = CGRectMake(0, 0, kTitleWidth, _height);
_bgView.frame = CGRectMake(kTitleWidth + 10, 0, _width - kTitleWidth - kCancleWidth - 20, _height);
_closeBtn.frame = CGRectMake(_width - kCancleWidth, 0, kCancleWidth, _height);
self.animationView.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height);
}
if (_bgView.width >= view.width) {
self.animationView.frame = CGRectMake((_bgView.width - view.width) / 2, 0, view.width, view.height);
}
[self.animationView addSubview:_contentView];
_animationViewWidth = self.animationView.width;
_animationViewHeight = self.animationView.height;
}
#pragma mark ================== CAAnimationDelegate ==================
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if (_isRestart) {
if (self.delegate && [self.delegate respondsToSelector:@selector(marqueeView:animationDidStopFinished:)]) {
[self.delegate marqueeView:self animationDidStopFinished:flag];
}
}
_isRestart = YES;
}
- (void)startAnimation {
[self stopAnimation];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(dismiss) object:nil];
CGFloat bgWidth = CGRectGetWidth(_bgView.frame);
if (bgWidth >= _animationViewWidth) {
[self performSelector:@selector(dismiss) withObject:nil afterDelay:_duration ? : kDefaultDuration];
return;
}
dispatch_block_t block = ^ {
CGPoint pointRightCenter = CGPointMake(_animationViewWidth / 2, _animationViewHeight / 2);
CGPoint pointLeftCenter = CGPointMake(- _animationViewWidth / 2, _animationViewHeight / 2);
CGPoint fromPoint = self.marqueeDirection == MarqueeDirectionLeft ? pointRightCenter : pointLeftCenter;
CGPoint toPoint = self.marqueeDirection == MarqueeDirectionLeft ? pointLeftCenter : pointRightCenter;
self.animationView.center = fromPoint;
UIBezierPath *movePath = [UIBezierPath bezierPath];
[movePath moveToPoint:fromPoint];
[movePath addLineToPoint:toPoint];
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
moveAnimation.path = movePath.CGPath;
moveAnimation.removedOnCompletion = YES;
moveAnimation.duration = _duration ? : _animationViewWidth / 30.f * (1 / self.speed);
moveAnimation.delegate = self;
[self.animationView.layer addAnimation:moveAnimation forKey:@"animationViewPosition"];
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), block);
}
- (void)stopAnimation {
if (self.animationView.layer.animationKeys.count) {
[self.animationView.layer removeAllAnimations];
_isRestart = NO;
}
}
- (void)pauseAnimation {
[self pauseLayer:self.animationView.layer];
}
- (void)resumeAnimation {
[self resumeLayer:self.animationView.layer];
}
- (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)dismiss {
[self.animationView.layer removeAllAnimations];
_isRestart = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(dismiss) object:nil];
if (self.delegate && [self.delegate respondsToSelector:@selector(closeMarqueeView:)]) {
[self.delegate closeMarqueeView:self];
}
}
#pragma mark ================== setter and getter method ==================
- (UIView *)bgView {
if (!_bgView) {
_bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _width, _height)];
_bgView.layer.masksToBounds = YES;
}
return _bgView;
}
- (UIView *)animationView {
if (!_animationView) {
_animationView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _width, _height)];
}
return _animationView;
}
- (UIButton *)titleBtn {
if (!_titleBtn) {
UIButton *titleBtn = [[UIButton alloc] initWithFrame:CGRectMake(- kTitleWidth, 0, kTitleWidth, _height)];
titleBtn.layer.cornerRadius = _height / 2;
titleBtn.layer.masksToBounds = YES;
[titleBtn setBackgroundColor:kDefTintColor];
[titleBtn setTitle:@"公告:" forState:UIControlStateNormal];
[titleBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
titleBtn.titleLabel.font = Font(14);
[titleBtn setImage:Image(@"announcementSound") forState:UIControlStateNormal];
_titleBtn = titleBtn;
}
return _titleBtn;
}
- (UIButton *)closeBtn {
if (!_closeBtn) {
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(_width, 0, kCancleWidth, _height)];
[btn setImage:Image(@"announcementClose") forState:UIControlStateNormal];
[btn addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
_closeBtn = btn;
}
return _closeBtn;
}
@end
使用
self.marqueeView = [[MarqueeView alloc] initWithFrame:CGRectMake(0, 0, 300, 30)];
_marqueeView.backgroundColor = RGBACOLOR(0, 0, 0, 0.7);
_marqueeView.duration = 10;
_marqueeView.delegate = self;
_marqueeView.center = self.view.center;
[self.view addSubview:_marqueeView];
_marqueeView.isNeedTitle = YES;
[_marqueeView addContentView:[self createLabelWithText:@"XXXXXXXXXXXXXXXXXXXXX"]];
[_marqueeView startAnimation];
/// 更换
- (void)clickAction:(UIButton *)btn {
[_marqueeView stopAnimation];
[_marqueeView addContentView:[self createLabelWithText:@"haha"]];
[_marqueeView startAnimation];
}