先看例子
每个弹幕的速度都是不一样的,支持弹幕整体开始暂停。
如果弹幕实在是太多了,有个缓冲队列,不停的重试能否显示,保证文字都能显示全,并且每条都能显示。
实现是基于 CADisplayLink 实现的,如此来说比直接搞个定时器来计算偏移丝滑,简单的平移动画如下:
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
}
}
在这个基础版本上稍微改了改就变成如下代码:
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
}
}
}
}
再找个需要使用的地方加入如下使用的代码,即可实现上图的效果
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