如何为Roku的Poster组件添加滑动切换动画
Roku频道轮播添加滑动切换动画实现方案
需求背景
我正在开发Roku频道,现有逻辑为:通过媒体(图片&视频)数组,按设定的超时秒数轮播内容(例如:一张图片显示5秒后切换至下一个内容)。目前已实现切换时的淡入淡出动画,现需添加滑动切换动画。
现有代码问题
- XML中滑动动画误用
FloatFieldInterpolator,但translation是二维向量属性,需用Vec2FieldInterpolator才能控制位置变化 - 滑动动画未设置合理的起始/结束位置,无法实现滑动效果
- BrightScript缺少动画类型判断逻辑,无法切换使用淡入淡出或滑动动画
修改后的XML代码
<component name="RendererScreen" extends="Group"> <script type="text/brightscript" uri="RendererScreen.bs" /> <children> <Poster id="backgroundImg" width="1920" height="1080" uri="pkg:/images/background.png" /> <Poster id="imgPoster" width="1920" height="1080" translation="[0, 0]" opacity="0.0" visible="true" /> <!-- 淡入淡出动画 --> <Animation id="fadeInAnimation" repeat="false" control="stop" easeFunction="linear"> <FloatFieldInterpolator id="fadeInInterpolator" key="[0.0, 1.0]" keyValue="[0.0, 1.0]" fieldToInterp="imgPoster.opacity" /> </Animation> <Animation id="fadeOutAnimation" repeat="false" control="stop" easeFunction="linear"> <FloatFieldInterpolator id="fadeOutInterpolator" key="[0.0, 1.0]" keyValue="[1.0, 0.0]" fieldToInterp="imgPoster.opacity" /> </Animation> <!-- 滑动动画:横屏从右侧滑入、左侧滑出 --> <Animation id="slideInAnimation" repeat="false" control="stop" easeFunction="linear"> <Vec2FieldInterpolator id="slideInInterpolator" key="[0.0, 1.0]" keyValue="[ [1920, 0], [0, 0] ]" fieldToInterp="imgPoster.translation" /> </Animation> <Animation id="slideOutAnimation" repeat="false" control="stop" easeFunction="linear"> <Vec2FieldInterpolator id="slideOutInterpolator" key="[0.0, 1.0]" keyValue="[ [0, 0], [-1920, 0] ]" fieldToInterp="imgPoster.translation" /> </Animation> <Video id="videoPlayer" width="1920" height="1080" translation="[0, 0]" visible="false" /> </children> </component>
修改后的BrightScript代码
sub init() m.poster = m.top.findNode("imgPoster") m.backgroundImg = m.top.findNode("backgroundImg") m.mediaList = [] m.currentIndex = 0 m.deviceOrientation = "90" m.animationType = "slide" // 可设置为"fade"切换回淡入淡出动画 fetchPlaylistData() end sub sub showMedia() if (m.mediaList.count() = 0) return end if mediaItem = m.mediaList[m.currentIndex] mediaType = mediaItem.type mediaUrl = mediaItem.url timeoutSeconds = mediaItem.timeoutSeconds m.backgroundImg.visible = false animationDuration = 1 // 获取所有动画节点 fadeInAnimation = m.top.findNode("fadeInAnimation") fadeOutAnimation = m.top.findNode("fadeOutAnimation") slideInAnimation = m.top.findNode("slideInAnimation") slideOutAnimation = m.top.findNode("slideOutAnimation") // 统一设置动画时长 fadeInAnimation.duration = animationDuration fadeOutAnimation.duration = animationDuration slideInAnimation.duration = animationDuration slideOutAnimation.duration = animationDuration // 根据设备方向调整滑动动画坐标 adjustSlideAnimationPositions() if (mediaType = "image") // 根据动画类型选择入场动画 if m.animationType = "fade" fadeInAnimation.control = "start" else if m.animationType = "slide" // 重置海报到滑动起始位置 m.poster.translation = [1920, 0] slideInAnimation.control = "start" end if displayImage(mediaUrl) else if (mediaType = "video") displayVideo(mediaUrl) end if startSlideShowTimer(timeoutSeconds) end sub sub adjustSlideAnimationPositions() slideInInterpolator = m.top.findNode("slideInInterpolator") slideOutInterpolator = m.top.findNode("slideOutInterpolator") if m.deviceOrientation = "90" or m.deviceOrientation = "270" // 竖屏模式改为上下滑动 slideInInterpolator.keyValue = [ [0, 1080], [0, 0] ] slideOutInterpolator.keyValue = [ [0, 0], [0, -1080] ] else // 横屏模式保持左右滑动 slideInInterpolator.keyValue = [ [1920, 0], [0, 0] ] slideOutInterpolator.keyValue = [ [0, 0], [-1920, 0] ] end if end sub sub displayVideo(url as string) if (m.videoPlayer = invalid) m.videoPlayer = m.top.findNode("videoPlayer") m.videoPlayer.observeField("state", "onVideoStateChange") end if if (m.videoPlayer <> invalid) videoContent = createObject("roSGNode", "ContentNode") videoContent.url = url m.videoPlayer.content = videoContent m.videoPlayer.visible = true m.videoPlayer.control = "play" else print "Error: Video node not found." end if end sub sub onVideoStateChange() if (m.videoPlayer.state = "error") m.videoPlayer.control = "stop" m.videoPlayer.visible = false startErrorDelayTimer() end if end sub sub displayImage(url as string) if (m.poster <> invalid) if(m.deviceOrientation = "90") m.poster.width = "1080" m.poster.height = "1920" m.poster.translation = [420, -420] m.poster.rotation = -1.570795 // -> π/2 else if(m.deviceOrientation = "180") m.poster.width = "1920" m.poster.height = "1080" m.poster.translation = [0, 0] m.poster.rotation = 3.14159 // -> π else if(m.deviceOrientation = "270") m.poster.width = "1080" m.poster.height = "1920" m.poster.translation = [420, -420] m.poster.rotation = 1.570795 // -> π/2 else // 默认横屏状态 m.poster.width = "1920" m.poster.height = "1080" m.poster.translation = [0, 0] m.poster.rotation = 0 end if m.poster.scaleRotateCenter = [m.poster.width / 2, m.poster.height / 2] m.poster.uri = url m.poster.observeField("loadStatus", "onPosterStateChange") m.poster.visible = true else print "Error: Poster node not found." end if end sub sub onPosterStateChange() if (m.poster.loadStatus = "failed") m.poster.visible = false startErrorDelayTimer() end if end sub sub startErrorDelayTimer() if (m.errorDelayTimer = invalid) m.errorDelayTimer = createObject("roSGNode", "Timer") m.errorDelayTimer.observeField("fire", "onErrorDelayComplete") m.top.appendChild(m.errorDelayTimer) end if m.errorDelayTimer.duration = 3 m.errorDelayTimer.repeat = false m.errorDelayTimer.control = "start" end sub sub onErrorDelayComplete() m.currentIndex = (m.currentIndex + 1) mod m.mediaList.count() showMedia() end sub sub startSlideShowTimer(duration as integer) if (m.timer = invalid) m.timer = createObject("roSGNode", "Timer") m.timer.observeField("fire", "onTimerFired") m.top.appendChild(m.timer) end if m.timer.duration = duration m.timer.repeat = false m.timer.control = "start" end sub sub onTimerFired() fadeOutAnimation = m.top.findNode("fadeOutAnimation") slideOutAnimation = m.top.findNode("slideOutAnimation") // 根据动画类型选择退场动画 if m.animationType = "fade" fadeOutAnimation.observeField("completion", "onAnimationComplete") fadeOutAnimation.control = "start" else if m.animationType = "slide" slideOutAnimation.observeField("completion", "onAnimationComplete") slideOutAnimation.control = "start" end if startOutTimer(animationDuration) end sub sub startOutTimer(animationDuration as integer) if (m.animationTimer = invalid) m.animationTimer = createObject("roSGNode", "Timer") m.animationTimer.observeField("fire", "onAnimationComplete") m.top.appendChild(m.animationTimer) end if m.animationTimer.duration = animationDuration m.animationTimer.repeat = false m.animationTimer.control = "start" end sub sub onAnimationComplete() if (m.videoPlayer <> invalid) m.videoPlayer.control = "stop" m.videoPlayer.visible = false end if // 重置海报状态,避免影响下一轮动画 m.poster.opacity = 0.0 m.poster.translation = [0, 0] m.currentIndex = (m.currentIndex + 1) mod m.mediaList.count() showMedia() end sub
关键修改说明
- XML动画修正:将滑动动画的插值器替换为
Vec2FieldInterpolator,设置合理的滑动坐标(横屏左右滑动、竖屏上下滑动) - 动画类型切换:新增
m.animationType变量,可快速切换淡入淡出/滑动动画模式 - 方向适配:添加
adjustSlideAnimationPositions()函数,根据设备旋转方向调整滑动坐标,适配横竖屏场景 - 回调统一处理:将原有的淡出回调改为通用的动画完成回调,统一处理两种动画的结束逻辑
- 状态重置:动画结束后重置海报的透明度和位置,避免残留状态影响下一轮轮播
内容的提问来源于stack exchange,提问作者Suleyman
相关产品推荐
相关产品推荐

