iOS 跑马灯带图片可点击

项目中有个需求,需要以跑马灯的形势滚动展示用户的实时数据,跑马灯需要有用户头像,内容的长度不固定,并且可以点击,滚动效果还要足够流畅,本着不重复造轮子的心理,在网上各种搜索,发现都没法找到满足需求的demo,没办法,本来(ˇˍˇ) 想~偷个懒来着,现在只有自己动手造轮子了。

整体思路大概就是在scrollView中添加多个view,让这几个view依次排列在在scrollView中,动态计算scrollView的frame,让其宽度刚好是所有view的总宽度和,然后把scrollView的x初始值设置在屏幕以外,通过一个定时器,让scrollView每隔一段时间就移动一定的距离,这个时间可以微调,确保不会出现滚动的时候抖动的问题,当scrollView滚出可见区域了,再把scrollView的frame恢复到初始值即可

先看下效果图吧,由于博客园好像不支持gif图片上传,所有整了几张静态图,如果想要完整效果和代码的话,可以前往我的github https://github.com/qqcc1388/MarqueeViewDemo 查看demo源码和动态效果

我的这个demo中分为3个部分

  • HXQMarqueeView 用来显示跑马灯的显示区域,接受滚动的数据源,并且手动控制动画的开启。
  • HXQBoardView 跑马灯中每组数据的显示区域,这个视图的长度是根据传入文字的多少,动态计算的,如果文字或者头像被点击了,可以通过block将点击的model传递到上一层
  • HXQMarqueeModel 跑马灯数据model 主要参数是文字内容和头像参数(头像是网络图片),设置完文字后,在setTitle这个方法中会动态的把文字的总宽度计算一遍,并赋值为titleWith,width的宽度为文字+头像的总宽度

部分实现代码

//
//  HXQMarqueeView.m
//  hxquan
//
//  Created by Tiny on 2018/3/2.
//  Copyright © 2018年 Tiny. All rights reserved.
//

#import "HXQMarqueeView.h"
#import "HXQMarqueeModel.h"
#import "HXQBoardView.h"
#import "UIView+Extionsiton.h"

@interface HXQMarqueeView ()

@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableArray *viewList;
@property (nonatomic, strong) NSArray *models;
@property (nonatomic, copy) void (^itelClick)(HXQMarqueeModel *);


@end

@implementation HXQMarqueeView

-(void)dealloc{
    [self.timer  invalidate];
    self.timer = nil;
}

-(NSMutableArray *)viewList{
    if (!_viewList) {
        _viewList = [NSMutableArray array];
    }
    return _viewList;
}

-(NSTimer *)timer{
    if (!_timer) {
        _timer = [NSTimer timerWithTimeInterval:0.008f target:self selector:@selector(refreshProgress) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    return _timer;
}

-(instancetype)initWithFrame:(CGRect)frame{
    
    if (self = [super initWithFrame:frame]) {
        
        [self setupUI];
    }
    return self;
}

-(void)setupUI{
    self.scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
    self.scrollView.scrollEnabled = NO;
    self.scrollView.showsVerticalScrollIndicator = NO;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    [self addSubview:self.scrollView];
}

-(void)setItems:(NSArray *)items{

    _models = items;
    //移除动画
    [self.scrollView.layer removeAllAnimations];
    
    //先移除之前的item
    for (UIView *v in self.scrollView.subviews) {
        if ([v isKindOfClass:[HXQBoardView class]]) {
            [v removeFromSuperview];
        }
    }
    [self.viewList removeAllObjects];
    
    //创建新的item
    HXQBoardView *last = nil;
    CGFloat margin = 20;
    for (int i = 0; i < items.count; i++) {
        HXQMarqueeModel *model = items[i];
        HXQBoardView * lb = [[HXQBoardView alloc] initWithFrame:CGRectMake(last.frame.origin.x + last.bounds.size.width + margin, 0, model.width, 44) Model:model];
        __weak typeof(self) weakself = self;
        lb.boardItemClick = ^(HXQMarqueeModel *xModel) {
            if (weakself.itelClick) {
                weakself.itelClick(xModel);
            }
        };
        lb.tag = i;
        [self.scrollView addSubview:lb];
        [self.viewList addObject:lb];
        last = lb;
    }
    //设置scrollView的contentSize
    self.scrollView.contentSize = CGSizeMake(last.frame.origin.x+last.bounds.size.width, 0);
    CGSize contetnsize = self.scrollView.contentSize;
    self.scrollView.frame = CGRectMake(self.bounds.size.width,0,contetnsize.width+self.bounds.size.width, 44);
    self.clipsToBounds = YES;
}

-(void)refreshProgress{
    
    self.scrollView.x -=0.5 ;
    if (self.scrollView.x <= -self.scrollView.contentSize.width) {
        self.scrollView.x = self.bounds.size.width;
    }
    
}

- (void)startAnimation {
    if (!self.timer.isValid) {
        [self.timer fire];
    }
}

-(void) stopAnimation{  //结束动画
    if (self.timer.isValid) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

#pragma mark - Private
-(void)addMarueeViewItemClickBlock:(void (^)(HXQMarqueeModel *))block{
    self.itelClick = block;
}

//demo使用起来也很简单 只需要3行代码即可(前提是数据源要准备好哦😯)

    //创建跑马灯
    HXQMarqueeView *marqueeView = [[HXQMarqueeView alloc] initWithFrame:CGRectMake(0 100,self.view.bounds.size.width, 44)];
    [self.view addSubview:marqueeView];
    //初始化数据源
    [marqueeView setItems:modelList];
    //开始动画
    [marqueeView startAnimation];
    //如果需要监听点击回调,请实现这个方法
    [marqueeView addMarueeViewItemClickBlock:^(HXQMarqueeModel *model) {
        NSLog(@"%@",model.title);
    }];

swift版本(snapKit布局),更加简洁完善

//
//  HXQMarqueeView.swift
//  hxquan-swift
//
//  Created by Tiny on 2018/11/20.
//  Copyright © 2018年 hxq. All rights reserved.
//  跑马灯

import UIKit

class MarqueeModel: Equatable{
    
    var title: String?  //内容
    
    var img: String?  //头像图片 url
    
    var textColor: UIColor = .black   //字体默认颜色
    
    var font: UIFont = .systemFont(ofSize: 14)  //字体大小

    var imageHolder: UIImage = HXQDefaultUserImage    //头像默认图片
    
    static func == (lhs: MarqueeModel, rhs: MarqueeModel) -> Bool {
        return  lhs.title == rhs.title &&
                lhs.img == rhs.img &&
                lhs.textColor == rhs.textColor &&
                lhs.font == rhs.font &&
                lhs.imageHolder == rhs.imageHolder
    }
}

class MarqueeItem: UIView {
    
    private var textLb: UILabel!  //文字label
    
    private var imgView: UIImageView!  //图片
    
    /// 重写setModel并赋值
    fileprivate var model: MarqueeModel?{
        didSet{
            if model != nil {
                textLb.text = model?.title
                textLb.textColor = model!.textColor
                textLb.font = model!.font
                imgView.sd_setImage(with: URL(string: model!.img ?? ""), placeholderImage: model!.imageHolder)
            }
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    private func setupUI(){
        
        let gesture = UITapGestureRecognizer(target: self, action: #selector(itemClick))
        addGestureRecognizer(gesture)
        textLb = UILabel()
        textLb.font = UIFont.systemFont(ofSize: 14)
        textLb.textColor = UIColor.black
        addSubview(textLb)
        
        imgView = UIImageView()
        imgView.layer.masksToBounds = true
        addSubview(imgView)
        
        imgView.snp.makeConstraints { (make) in
            make.left.equalTo(5)
            make.top.equalTo(5)
            make.bottom.equalTo(-5)
            make.width.equalTo(imgView.snp.height)
        }
        
        textLb.snp.makeConstraints { (make) in
            make.centerY.equalToSuperview()
            make.left.equalTo(imgView.snp.right).offset(5)
            make.right.equalTo(-5)
        }
    }
    /// item被点击事件回调
    fileprivate var itemDidTap:(() -> Void)?
    
    @objc private func itemClick(){
        //将事件传递出去
        itemDidTap?()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        imgView.layer.cornerRadius = imgView.bounds.size.width*0.5
    }
}


class HXQMarqueeView: UIView {
    
    /// 初始化scrollView
    private lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView(frame: .zero)
        scrollView.scrollsToTop = false
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = false
        return scrollView
    }()
    
    
    /// 初始化定时器
    private lazy var timer: Timer = {[unowned self] in
        let timer = Timer(timeInterval: 0.008, target: self, selector: #selector(startToMove), userInfo: nil, repeats: true)
        RunLoop.current.add(timer, forMode: .common)
        return timer
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    private func setupUI(){
        layer.masksToBounds = true
        
        addSubview(scrollView)
        scrollView.snp.makeConstraints { (make) in
            make.top.left.bottom.equalToSuperview()
            make.width.equalToSuperview()
        }
    }
    
    var marqueeHolder: String?
    
    var marqueeFontSize: CGFloat = 14
    
    var marqueeTextColor = UIColor.black
    
    public var items = [MarqueeModel](){
        didSet{
            
            //判断2次数据是否相同
            if oldValue == items {
                return
            }
            
            //关闭定时器
            if timer.isValid {
                timer.fireDate = Date.distantFuture
            }
            //移除scrollView中所有控件
            for v in scrollView.subviews{
                v.removeFromSuperview()
            }
            //显示默认
            if items.isEmpty{
                //如果需要显示默认值的话
                if marqueeHolder != nil{
                    let lb = UILabel()
                    lb.textAlignment = .center
                    lb.text = marqueeHolder
                    lb.font = UIFont.systemFont(ofSize: marqueeFontSize)
                    lb.textColor = marqueeTextColor
                    scrollView.addSubview(lb)
                    lb.snp.makeConstraints { (make) in
                        make.height.equalToSuperview()
                        make.left.equalTo(20)
                    }
                    //更新scrollView的ContentSize
                    scrollView.snp.makeConstraints { (make) in
                        make.right.equalTo(lb.snp.right)
                    }
                    layoutIfNeeded()
                    scrollView.x = 0;
                }
            }else{
                let margin: CGFloat = 10
                let gap: CGFloat = 20
                var last: MarqueeItem?
                for (i,model) in items.enumerated(){
                    let item = MarqueeItem()
                    item.model = model
                    item.itemDidTap = { [unowned self] in
                        self.selectionBlock?(model,i)
                    }
                    scrollView.addSubview(item)
                    item.snp.makeConstraints { (make) in
                        if last == nil{
                            make.left.equalTo(margin)
                        }else{
                            make.left.equalTo(last!.snp.right).offset(gap)
                        }
                        make.height.equalToSuperview()
                    }
                    last = item
                }
                //更新scrollView的ContentSize
                scrollView.snp.makeConstraints { (make) in
                    make.right.equalTo(last!.snp.right).offset(margin)
                }
                
                layoutIfNeeded()
                
                //拿到contentSize重新更新scrollView约束
                scrollView.snp.remakeConstraints { (make) in
                    make.width.equalTo(scrollView.contentSize.width)
                    make.top.bottom.equalToSuperview()
                    make.right.equalTo(last!.snp.right).offset(margin)
                    make.left.equalTo(self.snp.right)
                }
                //开始启动定时器
                if last != nil{
                    timer.fireDate = Date(timeIntervalSinceNow: 0)
                }
            }
        }
    }
    
    private var selectionBlock: ((MarqueeModel,Int) -> Void)?
    
    public func queeSelection(_ callBack: ((MarqueeModel,Int) -> Void)?){
        selectionBlock = callBack
    }
    
    @objc private func startToMove(){
        scrollView.x = scrollView.x - 0.5 ;
        if scrollView.x <= -scrollView.contentSize.width {
            scrollView.x = self.bounds.size.width;
        }
    }
    
    deinit {
        if timer.isValid {
            timer.invalidate()
        }
    }
}

/// swift版本跑马灯使用方法
        //创建
        let marquee = HXQMarqueeView()
        view.addSubview(marquee)
        //设置约束
        marquee.snp.makeConstraints { (make) in
            make.left.equalTo(20)
            make.right.equalTo(-20)
            make.top.equalTo(100)
            make.height.equalTo(30)
        }
        //初始化数据源
        var array = [MarqueeModel]()
        for i in 0..<5 {
            let item = MarqueeModel()
            item.title = "我完事了,你们呢\(i)"
            item.img = ""
            item.textColor = COLOR_RANDOM()
            item.font = HFont(12)
            array.append(item)
        }
        //赋值
        marquee.items = array
        //监听点击
        marquee.queeSelection { (model, index) in
            print("\(model.title ?? "") + \(index)")
        }

更多详情请参考demo: https://github.com/qqcc1388/MarqueeViewDemo

posted @ 2018-03-28 15:50  qqcc1388  阅读(2114)  评论(0编辑  收藏  举报