You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Roku的Poster组件添加滑动切换动画

Roku频道轮播添加滑动切换动画实现方案

需求背景

我正在开发Roku频道,现有逻辑为:通过媒体(图片&视频)数组,按设定的超时秒数轮播内容(例如:一张图片显示5秒后切换至下一个内容)。目前已实现切换时的淡入淡出动画,现需添加滑动切换动画。

现有代码问题

  1. XML中滑动动画误用FloatFieldInterpolator,但translation是二维向量属性,需用Vec2FieldInterpolator才能控制位置变化
  2. 滑动动画未设置合理的起始/结束位置,无法实现滑动效果
  3. 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

关键修改说明

  1. XML动画修正:将滑动动画的插值器替换为Vec2FieldInterpolator,设置合理的滑动坐标(横屏左右滑动、竖屏上下滑动)
  2. 动画类型切换:新增m.animationType变量,可快速切换淡入淡出/滑动动画模式
  3. 方向适配:添加adjustSlideAnimationPositions()函数,根据设备旋转方向调整滑动坐标,适配横竖屏场景
  4. 回调统一处理:将原有的淡出回调改为通用的动画完成回调,统一处理两种动画的结束逻辑
  5. 状态重置:动画结束后重置海报的透明度和位置,避免残留状态影响下一轮轮播

内容的提问来源于stack exchange,提问作者Suleyman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 04:19:55