IBarrageView
interface IBarrageView {
// 弹幕是否可点击
fun setEnableTouch(enable: Boolean)
// 添加弹幕控制器
fun addController(controller: AbsBarrageController, priority: Int = 0)
// 移除弹幕控制器
fun removeController(controller: AbsBarrageController)
fun clear()
}
BarrageView
- 内部维护 List<BarrageController>
- 重写 onDraw、onTouchEvent 和 onDetachedFromWindow,进而调用 BarrageController 对应方法
class BarrageView {
override fun onDraw(canvas: Canvas) {
if (controllerList.size == 0) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
}
loopWithIndex(controllerList.size) {
controllerList[it].apply {
draw(canvas)
}
false
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (mEnableTouch) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mTouchController = null
for (barrageController in controllerList) {
if (barrageController.onTouchEvent(event)) {
mTouchController = barrageController
return true
}
}
}
MotionEvent.ACTION_UP -> {
mTouchController?.let {
return it.onTouchEvent(event)
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_MOVE -> {
mTouchController?.let {
return it.onTouchEvent(event)
}
}
}
return super.onTouchEvent(event)
} else {
return super.onTouchEvent(event)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow() // 清理 Controller,防止空指针
controllerList.forEach {
it.release()
}
controllerList.clear()
}
}
AbsBarrageController
内部维护三个队列,分别为 waitingList、preparingList、runningList
内部维护计时器,通过 AnimationUpdateListener 计算时间差,并调用 onPlayBarrage 抽象方法,如果三个队列都为空,则停止计时器
abstract fun onPlayBarrage(runningList: MutableList<AbsBarrage>, deltaTime: Float)
@MainThread
private fun initAnimation(): ValueAnimator {
val animator = ValueAnimator.ofFloat(0F, AbsBarrageController.ANIMATOR_DURATION.toFloat())
animator.interpolator = LinearInterpolator()
animator.duration = AbsBarrageController.ANIMATOR_DURATION.toLong()
animator.repeatMode = ValueAnimator.RESTART
animator.repeatCount = ValueAnimator.INFINITE
animator.addUpdateListener { animation ->
val oldAnimatorValue = oldAnimatorValue
val animatorValue = animation.animatedValue as Float
this.oldAnimatorValue = animatorValue
var deltaTime = animatorValue - oldAnimatorValue
if (animatorValue < oldAnimatorValue) {
deltaTime = AbsBarrageController.ANIMATOR_DURATION - oldAnimatorValue + animatorValue
}
onPlayBarrage(runningList, deltaTime)
if (waitingList.isEmpty() && preparingList.isEmpty() && runningList.isEmpty()) {
stop()
}
barrageView.invalidate()
}
return animator
}
另外,Controller 也提供了 resume、pause 方法,用于在 UI 状态改变时启动/停止计时器
@MainThread
fun start() {
checkMainThread()
if (valueAnimator.isPaused) {
valueAnimator.resume()
}
}
@MainThread
fun stop() {
checkMainThread()
if (!valueAnimator.isPaused && valueAnimator.isRunning) {
valueAnimator.pause()
}
}
添加弹幕接口,在该方法中首先会判断是否启动计时器,接着会判断是否需要将 waitingList 中的 Barrage 升级为 prepare 状态
fun addBarrage(barrage: AbsBarrage, addToFront: Boolean = false) {
checkMainThread()
if (addToFront) {
waitingList.addFirst(barrage)
} else {
waitingList.add(barrage)
}
barrage.state = AbsBarrage.State.ADDED
start()
}
@MainThread
private fun start() {
checkMainThread()
if (!valueAnimator.isRunning) {
valueAnimator.start()
}
notifyPrepare()
}
private fun notifyPrepare() {
while (preparingList.size <= AbsBarrageController.PRE_BUILD_COUNT && waitingList.size != 0) {
val barrage = waitingList.removeFirst()
if (barrage.isHighPriority) {
preparingList.addFirst(barrage)
} else {
preparingList.add(barrage)
}
barrage.state = AbsBarrage.State.PREPARING
}
}
onPlayBarrage 的实现方法会维护的各个 Barrage 位置,需要动态获取 preparingList 中的 Barrage,并添加到 runningList 中,此时 AbsBarrageController 会判断是否将 waitingList 中的 Barrage 升级为 prepare 状态。同时 onPlayBarrage 的实现方法也需要负责 running 状态的 Barrage 从 runningList 中移除
@MainThread
protected fun nextPreparedBarrage(): AbsBarrage? {
val iterator = preparingList.iterator()
while (iterator.hasNext()) {
val barrage = iterator.next()
if (barrage.readyToDraw) {
iterator.remove()
notifyPrepare()
return barrage
}
}
return null
}
另外,AbsBarrageController 提供了 draw 和 onEventTouch 方法,方法内部会调用 runningList 的对应方法
@MainThread
internal fun draw(canvas: Canvas) {
loopWithIndex(runningList.size) {
runningList[it].draw(canvas)
false
}
}
RandomLineBarrageController
在 AbsBarrageController 的具体实现类中,我们需要实现 onPlayBarrage 方法,并通过 runningList 中的 Barrage 实时位置,决定是否添加新的 Barrage,并设置新添加 Barrage 的初始位置,或者 remove 掉 running 的 Barrage
class RandomLineBarrageController(
private val firstLinePaddingTop: Int,
private val minIntervalDistance: Int,
private val lineHeight: Int,
private val barrageView: BarrageView,
private val barrageLines: Int,
private val minIntervalTime: Int
) {
override fun onPlayBarrage(runningList: MutableList<AbsBarrage>, deltaTime: Float) {
...
// 删除 running barrage
if (barrage.rectF.right < 0) {
iterator.remove()
}
...
// 添加 running barrage,并设置初始位置
val absBarrage = nextPreparedBarrage() ?: return@loopWithIndex false
val width = absBarrage.rectF.width()
val height = absBarrage.rectF.height()
absBarrage.rectF.apply {
left = canvasWidth.toFloat()
right = canvasWidth + width
top = (i * lineHeight + firstLinePaddingTop).toFloat()
bottom = i * lineHeight + firstLinePaddingTop + height
}
}
}
AbsBarrage
AbsBarrage 内部定义了 State 信息 和一些关键字段,其中,readyToDraw 用于标识当前 Barrage 是否已经准备好,rectF 用于记录 Barrage 位置信息
其中,子类主要需要实现 State 状态转移回调和 handleProgress 方法
abstract fun handleProgress(progress: Float, parentWidth: Int)
var readyToDraw = false
val rectF = RectF(0F, 0F, 0F, 0F)
@Volatile
var state: AbsBarrage.State = AbsBarrage.State.CREATED
set(state) {
val prev = field
field = state
moveToState(prev, state)
}
private fun moveToState(prev: AbsBarrage.State, state: AbsBarrage.State) {
if (prev == state) return
check(prev < state) { "Wrong state! Prev: $prev, Target: $state" }
when (state) {
AbsBarrage.State.CREATED -> throw IllegalStateException("Shouldn't set state to CREATED.")
AbsBarrage.State.ADDED -> onAdd()
AbsBarrage.State.PREPARING -> onPrepare()
AbsBarrage.State.VISIBLE -> onShow()
AbsBarrage.State.HIDDEN -> onHide()
}
}
open fun onAdd() { }
open fun onPrepare() { }
open fun onShow() { }
open fun onHide() { }
open fun draw(canvas: Canvas) {
val bm = bitmap ?: return
if (state != AbsBarrage.State.VISIBLE || bm.isRecycled) {
return
}
canvas.drawBitmap(
bm,
rectF.left + config.paddingHorizontal,
rectF.top + config.paddingVertical,
paint
)
}
ViewWrapperBarrage
实现 State 生命周期回调,生成并保存 Bitmap 信息,在 State.HIDDEN 时回收 Bitmap
override fun onHide() {
_bitmap?.let { config.bitmapProvider.recycle(it) }
}
private var _bitmap: Bitmap? = null
override val bitmap: Bitmap? get() = _bitmap
private val bitmapListener: BitmapListener = { bitmap: Bitmap, width: Int, height: Int ->
if (state >= AbsBarrage.State.HIDDEN) {
config.bitmapProvider.recycle(bitmap)
} else {
_bitmap = bitmap
setSize(width.toFloat(), height.toFloat())
readyToDraw = true
}
}
override fun onPrepare() {
if (view == null) {
view = mProvideView(this)
}
view?.let {
updateView(it)
}
}
private fun updateView(view: View) {
if (_bitmap != null) return
this.view = view
if (state == AbsBarrage.State.PREPARING || state == AbsBarrage.State.VISIBLE) {
doConvertViewToBitmap(
view,
config.bitmapProvider,
config.workingExecutor,
bitmapListener
)
}
}
protected open fun doConvertViewToBitmap(
view: View,
bitmapProvider: BitmapProvider,
executor: Executor,
bitmapListener: BitmapListener
) {
convertViewToBitmapLocked(view, bitmapProvider, executor, bitmapListener)
}
AccelerateBarrage
- 传递需要渲染的 View 信息
- 实现了 handleProgress 方法,更新 rectF 位置
Utils
View 转换为 Bitmap 时,需要在线程中执行,并且需要加锁
internal inline fun convertViewToBitmapLocked(
view: View,
bitmapProvider: BitmapProvider,
executor: Executor,
crossinline listener: BitmapListener) {
executor.execute {
runLocking(convertBitmapLock) {
internalConvertViewToBitmap(view, bitmapProvider, listener)
}
}
}
手动调用 View.measure 和 View.layout,且需要所有子 View 调用 forceLayout
获取到 Bitmap 后,继续调用 View.draw 方法
private inline fun internalConvertViewToBitmap(
view: View,
bitmapProvider: BitmapProvider,
crossinline listener: BitmapListener) {
view.rForceLayout()
view.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
val width = view.measuredWidth
val height = view.measuredHeight
view.layout(0, 0, width, height)
runCatching {
bitmapProvider.obtain(
width,
height,
if (view.isOpaque) Bitmap.Config.RGB_565 else Bitmap.Config.ARGB_8888
).also { view.draw(Canvas(it)) }
}.getOrNull()?.let { bitmap ->
mainThreadHandler.post { listener(bitmap, width, height) }
}
}
private fun View.rForceLayout() {
forceLayout()
if (this is ViewGroup) {
for (i in 0 until childCount) {
getChildAt(i).rForceLayout()
}
}
}
BitmapProvider
通过 TreeSet 保存 Bitmap,内部会根据 Bitmap 大小进行排序,在获取 Bitmap 时,会获取最适合的大小
BitmapProvider 内部维护了一个定时器,每 3 秒中清除一张最大的缓存图
class CachedBitmapProvider @JvmOverloads constructor(
private val countLimit: Int = 3,
private val evictInterval: Long = 3000L
) : BitmapProvider {
private val pool: TreeSet<Bitmap>
get() = TreeSet { o1, o2 ->
if (o1 == o2) {
0
} else {
val result = o1.byteCount - o2.byteCount
if (result == 0) {
-1
} else {
result
}
}
}
@AnyThread
override fun obtain(w: Int, h: Int, config: Bitmap.Config): Bitmap {
return runLocking(lock) {
obtainSmallestAvailable(w, h, config) ?: Bitmap.createBitmap(w, h, config)
}
}
@AnyThread
override fun recycle(bitmap: Bitmap) {
runLocking(lock) {
pool.add(bitmap)
if (pool.size > countLimit) {
pool.iterator().apply {
next().recycle()
remove()
}
}
delayedEvict()
}
}
}