iOS 跑马灯效果

介绍

因业务需求,需要做一个跑马灯效果的公告栏,查了网上大部分制作原理,都不能满足也无需求,决定自己造轮子

效果

MarqueeGif.gif

需求

  • 如果文字太多,跑马灯效果;否则,无动画中间位置显示
  • 默认跑马灯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];
}

你可能感兴趣的:(iOS 跑马灯效果)