前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >swift简单弹幕例子,仿哔哩哔哩

swift简单弹幕例子,仿哔哩哔哩

原创
作者头像
用户4923333
发布2023-07-21 15:40:04
2660
发布2023-07-21 15:40:04
举报
文章被收录于专栏:后段开发后段开发

先看例子

每个弹幕的速度都是不一样的,支持弹幕整体开始暂停。

如果弹幕实在是太多了,有个缓冲队列,不停的重试能否显示,保证文字都能显示全,并且每条都能显示。

实现是基于 CADisplayLink 实现的,如此来说比直接搞个定时器来计算偏移丝滑,简单的平移动画如下:

代码语言:swift
复制
import UIKit

class ViewController: UIViewController {
    
    let squareView = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 创建 CADisplayLink 对象
        let displayLink = CADisplayLink(target: self, selector: #selector(update))
        
        // 将视图控制器添加到 displayLink 中
        displayLink.add(self, for: .common)
        
        // 设置视图属性
        squareView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
        squareView.backgroundColor = UIColor.red.withAlphaComponent(0.5)
        view.addSubview(squareView)
    }
    
    @objc func update(_ displayLink: CADisplayLink) {
        // 在每一帧更新时移动视图
        squareView.frame.origin.x += 5
    }
}

在这个基础版本上稍微改了改就变成如下代码:

代码语言:swift
复制
import Foundation
import UIKit

class XDanMu {
    var row: Int = 0
    var label: UILabel = UILabel()
    var speed: CGFloat = 0
    var isMe: Bool = false
}

class XDanMuView: UIView {
    var displayLink: CADisplayLink?
    
    var lineHeight: CGFloat = 26
    var gap: CGFloat = 20
    var minSpeed: CGFloat = 1
    var maxSpeed: CGFloat = 2
    var isPause: Bool = false
    
    var danmus: [XDanMu] = []
    var danmuQueue: [(String, Bool)] = []
    var timer: Timer?
    
    func start() {
        displayLink = CADisplayLink(target: self, selector: #selector(update))
        displayLink?.add(to: RunLoop.current, forMode: .common)
        
        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleDanMuQueue), userInfo: nil, repeats: true)
    }
    
    @objc func handleDanMuQueue() {
        if danmuQueue.isEmpty {
            return
        }
        let danmu = danmuQueue.removeFirst()
        addDanMu(text: danmu.0, isMe: danmu.1)
    }
    
    @objc func addDanMu(text: String, isMe: Bool) {
        let danmu = XDanMu()
        danmu.label.frame.origin.x = self.frame.size.width
        danmu.label.text = text
        danmu.label.sizeToFit()
        
        if isMe {
            danmu.label.layer.borderWidth = 1
        }
        
        var linelasts: [XDanMu?] = []
        let rows: Int = Int(self.frame.size.height / lineHeight)
        for _ in 0..<rows {
            linelasts.append(nil)
        }
        
        for d in danmus {
            if d.row >= linelasts.count {
                break
            }
            if linelasts[d.row] != nil {
                let endx = danmu.label.frame.origin.x
                let targetx = linelasts[d.row]!.label.frame.origin.x
                if endx > targetx {
                    linelasts[d.row] = d
                }
            } else {
                linelasts[d.row] = d
            }
        }
        
        var isMatch = false
        for index in 0..<linelasts.count {
            if let d = linelasts[index] {
                let endx = d.label.frame.origin.x + d.label.frame.size.width + gap
                if endx < self.frame.size.width {
                    danmu.row = index
                    var ms = self.frame.size.width / endx * d.speed
                    ms = CGFloat.minimum(ms, maxSpeed)
                    danmu.speed = CGFloat.random(in: minSpeed...ms)
                    isMatch = true
                    break
                }
            } else {
                danmu.row = index
                danmu.speed = CGFloat.random(in: minSpeed...maxSpeed)
                isMatch = true
                break
            }
        }
        
        if isMatch == false {
            danmuQueue.append((text, isMe))
            return
        }
        
        danmu.label.frame.origin.y = lineHeight * CGFloat(danmu.row)
        
        self.addSubview(danmu.label)
        self.danmus.append(danmu)
    }
    
    @objc func update(_ displayLink: CADisplayLink) {
        if isPause == true {
            return
        }
        // 在每一帧更新时移动视图
        for index in 0..<danmus.count {
            let danmu = danmus[index]
            danmu.label.frame.origin.x -= danmu.speed
            if danmu.label.frame.origin.x < -danmu.label.frame.size.width {
                danmu.label.removeFromSuperview()
                danmus.remove(at: index)
                break
            }
        }
    }
}

再找个需要使用的地方加入如下使用的代码,即可实现上图的效果

代码语言:swift
复制
override func viewDidLoad() {
    super.viewDidLoad()
    var danmuView: XDanMuView = XDanMuView()
    danmuView.frame = .init(x: 0, y: 100, width: self.view.frame.size.width, height: self.view.frame.size.height - 200)
    self.view.addSubview(danmuView)

    // 配置项
    danmuView.minSpeed = 1
    danmuView.maxSpeed = 2
    danmuView.gap = 20
    danmuView.lineHeight = 30

    // 启动弹幕
    danmuView.start()
    // 启动一个定时器灌弹幕
    timer = Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(addDanMu), userInfo: nil, repeats: false)
}

@objc func addDanMu() {
    let interval = CGFloat.random(in: 0.3...1.0)
    Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(addDanMu), userInfo: nil, repeats: false)
    
    var text = ""
    for _ in 0...Int.random(in: 1...30) {
        text += "嘿"
    }
    for _ in 0...Int.random(in: 1...2) {
        danmuView.addDanMu(text: text, isMe: Bool.random())
    }
}

文本的字体自行根据需求修改,目前是没有增加样式跟颜色。

完整工程传送门

github

gitee

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档