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

轉載請標注來源:http://www.cnblogs.com/qqcc1388/p/8664280.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM