先看例子
每个弹幕的速度都是不一样的,支持弹幕整体开始暂停。
如果弹幕实在是太多了,有个缓冲队列,不停的重试能否显示,保证文字都能显示全,并且每条都能显示。
实现是基于 CADisplayLink 实现的,如此来说比直接搞个定时器来计算偏移丝滑,简单的平移动画如下:
import UIKitclass 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}
}
在这个基础版本上稍微改了改就变成如下代码:
import Foundation
import UIKitclass XDanMu {var row: Int = 0var label: UILabel = UILabel()var speed: CGFloat = 0var isMe: Bool = false
}class XDanMuView: UIView {var displayLink: CADisplayLink?var lineHeight: CGFloat = 26var gap: CGFloat = 20var minSpeed: CGFloat = 1var maxSpeed: CGFloat = 2var isPause: Bool = falsevar 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.widthdanmu.label.text = textdanmu.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.xlet targetx = linelasts[d.row]!.label.frame.origin.xif endx > targetx {linelasts[d.row] = d}} else {linelasts[d.row] = d}}var isMatch = falsefor index in 0..<linelasts.count {if let d = linelasts[index] {let endx = d.label.frame.origin.x + d.label.frame.size.width + gapif endx < self.frame.size.width {danmu.row = indexvar ms = self.frame.size.width / endx * d.speedms = CGFloat.minimum(ms, maxSpeed)danmu.speed = CGFloat.random(in: minSpeed...ms)isMatch = truebreak}} else {danmu.row = indexdanmu.speed = CGFloat.random(in: minSpeed...maxSpeed)isMatch = truebreak}}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.speedif danmu.label.frame.origin.x < -danmu.label.frame.size.width {danmu.label.removeFromSuperview()danmus.remove(at: index)break}}}
}
再找个需要使用的地方加入如下使用的代码,即可实现上图的效果
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 = 1danmuView.maxSpeed = 2danmuView.gap = 20danmuView.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