原始 HTML 报告:通用按钮延迟增强执行链路
通用按钮延迟增强执行链路
这篇笔记梳理通用按钮延迟增强逻辑:数据如何进入按钮、动画队列如何生成、播放进度和外部状态如何触发动画,以及按钮显隐性由哪一层控制。
核心链路:
数据来源 -> 对象创建 -> 初始化/绑定 -> 队列/状态准备 -> 外部触发 -> 分发 -> 具体执行 -> 回调/副作用数据与 transition
延迟增强阶段配置最终进入 NadGeneralButtonModel.transitions。每个阶段由 TransitionItem 表示,核心字段是 style、delay、duration。
class TransitionItem {
var style = 0
var delay: Long = 0
var duration: Long = 0
companion object {
const val INITITAL_STATE = 0
const val BTN_SHOW_STATE = 1
const val COLOR_CHANGE_STATE = 2
}
}在融合流场景中,老的 EnhanceModel.transition 会通过 NadGeneralButtonUtil.toSdkTransition() 转成 nadcore 的 TransitionItem,再填到 NadGeneralButtonModel.transitions。
父容器创建按钮并提供驱动入口
NadSummaryViewContainer 负责创建 NadGeneralBtnManagerView,并把它添加到延迟增强按钮容器中。父容器不实现动画细节。
val model = data.generalButton ?: NadGeneralButtonUtil.createGeneralBtnModel(context, data, enhanceModel)
if (mGeneralButtonView == null) {
mGeneralButtonView = NadGeneralBtnManagerView(
context,
NadFlowSummaryButtonHelper(store, model, mOnEnhanceBtnShowCallBack)
)
} else {
mGeneralButtonView?.updateData(model)
}
mTransitionButtonContainer.addView(mGeneralButtonView)驱动入口来自 NadSummaryComponent:
showButton.observe(this) { state ->
summaryView.getGeneralButtonView()?.updateTransitionState(state)
}
private fun showAdOperateButton(playerProgress: Int) {
summaryView.getGeneralButtonView()?.updateTransition(playerProgress)
}Manager 选择真实按钮并转发调用
NadGeneralBtnManagerView 根据 buttonType 管理两个真实按钮 View:普通按钮 NadGeneralButtonView 和下载按钮 NadDownloadGeneralButtonView。
open fun updateData(data: NadGeneralButtonModel) {
helper.updateModel(data)
val targetButtonType = helper.getButtonType()
if (currentButtonType?.value == targetButtonType) {
getCurrentButtonView()?.updateData(data)
return
}
switchButtonView(targetButtonType)
}Manager 不直接执行动画,只负责转发进度和状态:
open fun updateTransition(progress: Int) {
getCurrentButtonView()?.updateTransition(progress)
}
open fun updateTransitionState(style: Int) {
getCurrentButtonView()?.updateTransitionState(style)
}真实按钮绑定数据并进入 TransitionManager
真实按钮初始化或更新时都会走 bindData()。延迟增强绑定发生在最后一步 bindTransition()。
internal open fun bindData() {
bindTextData()
bindContainerData()
bindIconData()
bindLottieData()
bindPagData()
bindListener()
bindTransition()
}
protected open fun bindTransition() {
transitionManager.bindTransitions(
helper.getBackgroundPagUrl() != null,
false,
true
)
}这里仍然是准备阶段,不是播放阶段。
TransitionManager 根据配置生成动画队列
bindTransitions() 负责清理旧队列、处理直接最终态,并把每个 TransitionItem 转成一个 BaseTransitionItemRunnable 放入 queue。
fun bindTransitions(usePag: Boolean, isDowngrade: Boolean, shouldPlayEntrance: Boolean) {
reset()
if (helper.shouldApplyFinalState()) {
animator.applyFinalColor()
return
}
helper.getTransitions()?.forEach { item ->
when (item?.style) {
TransitionItem.INITITAL_STATE -> animator.animateInit(item)
TransitionItem.BTN_SHOW_STATE -> animator.animateShow(item)
TransitionItem.COLOR_CHANGE_STATE -> animator.animateColorChange(item, usePag, isDowngrade, shouldPlayEntrance)
}
}
}animateShow() 和 animateColorChange() 的名字容易误解。它们在这里主要是创建任务并入队,真正播放要等 onStartAnim() 被触发。
Runnable 的具体实现来自匿名子类
BaseTransitionItemRunnable 是抽象模板,本身没有具体动画。
abstract class BaseTransitionItemRunnable(delay: Long, duration: Long) : Runnable {
override fun run() {
onStartAnim()
}
abstract fun onStartAnim()
abstract fun onCancelAnim()
}具体逻辑由 NadButtonTransitionManager 里的匿名子类实现。创建 show 任务时,onStartAnim() 负责展示按钮并启动高度/淡入动画:
queue.add(object : BaseTransitionItemRunnable(item.delay, item.duration) {
override fun onStartAnim() {
helper.onBtnStateChange(TransitionItem.BTN_SHOW_STATE)
buttonView.visibility = VISIBLE
buttonLottie.visibility = VISIBLE
heightAnim?.start()
buttonView.startAnimation(fadeInAnimation)
}
})创建 color 任务时,onStartAnim() 负责播放背景 PAG 或启动颜色动画:
addTransition(object : BaseTransitionItemRunnable(item.delay, item.duration) {
override fun onStartAnim() {
if (usePag && bgPagView.isReady()) {
bgPagView.visibility = VISIBLE
bgPagView.play()
} else {
colorAnim?.start()
bgPagView.visibility = GONE
}
helper.onBtnStateChange(TransitionItem.COLOR_CHANGE_STATE)
buttonLottie.playAnimation()
}
})所以 queue 里虽然存的类型都是 BaseTransitionItemRunnable,但实际对象分别是 init/show/color 的匿名子类。调用 item.onStartAnim() 时,会动态分发到该对象自己的实现。
两种触发入口共用同一套队列
播放进度触发
update(progress) 按 currentIndex 顺序取当前任务。当播放进度达到 nextStartTime 时,执行当前任务的 onStartAnim(),然后推进索引。
fun update(progress: Int) {
val item = queue[currentIndex]
if (progress * 1000 >= nextStartTime) {
item.onStartAnim()
nextStartTime += (item.mDuration + item.mDelay).toInt()
currentIndex++
}
}外部状态触发
updateTransitionState(style) 按外部传入的 style 直接触发指定阶段,并手动修正 currentIndex,避免后续重复触发同一阶段。
fun updateTransitionState(style: Int) {
when (style) {
TransitionItem.BTN_SHOW_STATE -> {
if (currentIndex <= 1) {
queue.getOrNull(style)?.let {
it.onStartAnim()
currentIndex = 2
}
}
}
}
}两种触发入口不是互斥关系,而是共用同一套 queue/currentIndex:
updateTransition(progress)
按播放器进度顺序推进 queue
updateTransitionState(state)
按外部状态直接推动到某个阶段
currentIndex
记录已经走到哪个阶段,用来降低重复触发风险显隐规则
按钮显隐由两层控制:Manager 层负责切换真实按钮时隐藏旧 View;TransitionManager 层负责延迟展示动画中的真实显隐。
Manager 层
切换按钮类型时,Manager 会先隐藏所有真实按钮:
private fun hideAllButtonViews() {
generalButtonView?.visibility = GONE
downloadButtonView?.visibility = GONE
}如果没有展示动画,复用旧 View 时需要直接设置为 VISIBLE,否则切换后可能一直保持 GONE。
TransitionManager 层
带 BTN_SHOW_STATE 的展示动画在绑定时会先隐藏真实按钮;当 show runnable 被触发时再显示按钮,并启动高度动画和淡入动画。
fun animateShow(item: TransitionItem) {
buttonView.layoutParams?.height = 0
buttonView.visibility = GONE
buttonLottie.visibility = GONE
queue.add(object : BaseTransitionItemRunnable(item.delay, item.duration) {
override fun onStartAnim() {
buttonView.visibility = VISIBLE
buttonLottie.visibility = VISIBLE
heightAnim?.start()
buttonView.startAnimation(fadeInAnimation)
}
})
}⚠️
height = 0依赖layoutParams已存在。首次创建时外层 layoutParams 可能尚未挂上,因此稳定的隐藏依据是buttonView.visibility = GONE。
完整链路
1. 数据进入
EnhanceModel.transition / data.generalButton
↓
NadGeneralButtonModel.transitions
↓
buttonType = command / download
2. 父容器创建
NadSummaryViewContainer
↓
NadGeneralBtnManagerView(context, helper)
↓
addView 到 ad_video_enhance_btn
3. Manager 选择真实按钮
NadGeneralBtnManagerView.updateData()
↓
buttonType 相同:currentButtonView.updateData(data)
buttonType 不同:switchButtonView(type)
4. 真实按钮绑定
NadGeneralButtonView.updateData()
↓
bindData()
↓
bindTransition()
↓
transitionManager.bindTransitions(...)
5. 动画队列创建
bindTransitions()
↓
INITITAL_STATE -> animateInit() -> queue.add(initItem)
BTN_SHOW_STATE -> animateShow() -> queue.add(showItem)
COLOR_CHANGE_STATE -> animateColorChange() -> queue.add(colorItem)
6. 动画触发
进度触发:updateTransition(progress) -> transitionManager.update(progress)
状态触发:updateTransitionState(state) -> transitionManager.updateTransitionState(state)
↓
item.onStartAnim()
7. 具体动画执行
showItem.onStartAnim() -> VISIBLE + heightAnim + fadeIn
colorItem.onStartAnim() -> PAG / colorAnim + buttonLottie