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

基于ExoPlayer实现可滚动视频并无损裁剪为1:1比例的技术求助

嘿,我来帮你搞定这个ExoPlayer的需求!下面分核心模块拆解方案,代码示例和逻辑都给你安排得明明白白:

实现可滚动视频 + 1:1无损裁剪的ExoPlayer方案

我们分两个核心部分解决你的需求:根据视频尺寸动态添加滚动 和 1:1固定尺寸无损裁剪,还可以结合两者做模式切换。

一、动态实现滚动逻辑

要根据视频实际尺寸决定是否启用滚动,核心是监听视频尺寸变化,再和容器尺寸对比调整外层滚动容器的显示。

1. 基础布局结构

用NestedScrollView(支持垂直滚动)嵌套HorizontalScrollView(支持水平滚动),包裹PlayerView。这样能同时应对横竖方向的滚动需求:

<!-- 滚动容器:默认隐藏,根据视频尺寸动态显示 -->
<androidx.core.widget.NestedScrollView
    android:id="@+id/scrollContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical"
    android:visibility="gone">

    <HorizontalScrollView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scrollbars="horizontal">

        <!-- 核心视频播放视图 -->
        <com.google.android.exoplayer2.ui.PlayerView
            android:id="@+id/playerView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:resizeMode="none" />

    </HorizontalScrollView>

</androidx.core.widget.NestedScrollView>

2. 监听视频尺寸并启用滚动

通过ExoPlayer的Player.Listener获取视频原生尺寸,对比容器尺寸后动态调整滚动状态:

val playerView = findViewById<PlayerView>(R.id.playerView)
val scrollContainer = findViewById<NestedScrollView>(R.id.scrollContainer)
val horizontalScroll = scrollContainer.getChildAt(0) as HorizontalScrollView

// 初始化ExoPlayer
val player = ExoPlayer.Builder(this).build()
playerView.player = player

// 监听视频尺寸变化
player.addListener(object : Player.Listener {
    override fun onVideoSizeChanged(videoSize: VideoSize) {
        super.onVideoSizeChanged(videoSize)
        // 获取旋转后的视频原生宽高(已自动适配视频横竖屏)
        val videoWidth = videoSize.width
        val videoHeight = videoSize.height

        // 获取容器可用尺寸(这里以屏幕尺寸为例,你也可以替换为自定义容器尺寸)
        val displayMetrics = resources.displayMetrics
        val containerWidth = displayMetrics.widthPixels
        val containerHeight = displayMetrics.heightPixels

        // 判断是否需要滚动
        val needVerticalScroll = videoHeight > containerHeight
        val needHorizontalScroll = videoWidth > containerWidth

        // 显示/隐藏滚动容器
        scrollContainer.visibility = if (needVerticalScroll || needHorizontalScroll) View.VISIBLE else View.GONE
        // 控制滚动条显示
        horizontalScroll.isHorizontalScrollBarEnabled = needHorizontalScroll
        scrollContainer.isVerticalScrollBarEnabled = needVerticalScroll

        // 设置PlayerView为视频原生尺寸,确保滚动能显示完整内容
        playerView.layoutParams.width = videoWidth
        playerView.layoutParams.height = videoHeight
        playerView.requestLayout()
    }
})

// 设置播放源(替换成你的视频地址/本地路径)
val mediaItem = MediaItem.fromUri("your_video_url_or_path")
player.setMediaItem(mediaItem)
player.prepare()
player.play()

二、实现1:1固定尺寸无损裁剪

要做到无损裁剪,核心是让PlayerView固定为正方形,再用ExoPlayer的resizeMode="crop"模式——这种模式会直接裁剪视频的中心区域为1:1,不会缩放像素,完全保留画质。

1. 调整PlayerView布局

把PlayerView设置为固定正方形,这里提供两种常见方式:

  • 方式1:适配屏幕宽度的正方形
<com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/playerView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_aspectRatio="1:1"
    app:resizeMode="crop" /> <!-- 关键:居中裁剪为1:1 -->
  • 方式2:固定具体尺寸的正方形(比如300dp×300dp)
<com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/playerView"
    android:layout_width="300dp"
    android:layout_height="300dp"
    app:resizeMode="crop" />

2. 无损画质的关键要点

  • 确保PlayerView的尺寸不超过视频原生分辨率:比如视频是1080×1920(竖屏),PlayerView设为1080×1080,裁剪后取视频中间1080×1080区域,画质完全无损。
  • 避免使用fit/zoom等缩放模式,这些会改变像素导致画质损失,crop是直接截取原视频区域,不会有画质损耗。

三、两种模式的切换逻辑

如果需要让用户在「完整滚动查看」和「1:1裁剪查看」之间切换,可以加个按钮实现:

// 切换到1:1裁剪模式
btnSwitchToCrop.setOnClickListener {
    playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_CROP
    // 设置PlayerView为屏幕宽度的正方形
    val squareSize = resources.displayMetrics.widthPixels
    playerView.layoutParams.width = squareSize
    playerView.layoutParams.height = squareSize
    playerView.requestLayout()
    // 隐藏滚动容器
    scrollContainer.visibility = View.GONE
}

// 切换回完整滚动模式
btnSwitchToFull.setOnClickListener {
    playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_NONE
    // 恢复PlayerView为视频原生尺寸
    val videoSize = player.videoSize
    playerView.layoutParams.width = videoSize.width
    playerView.layoutParams.height = videoSize.height
    playerView.requestLayout()
    // 重新判断是否需要滚动
    val containerWidth = resources.displayMetrics.widthPixels
    val containerHeight = resources.displayMetrics.heightPixels
    val needScroll = videoSize.height > containerHeight || videoSize.width > containerWidth
    scrollContainer.visibility = if (needScroll) View.VISIBLE else View.GONE
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 16:18:18