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

如何在Compose中设置Media3文本叠加层边界实现自动换行?

问题描述

使用Media3 Transformer和ExoPlayer创建TextureOverlay静态文本导出视频时,目前仅能通过RelativeSizeSpan和ForegroundColorSpan设置文本大小与颜色,但无法限制文本的最大宽高,导致文本以单行形式超出视频范围,无法自动换行。

现有代码片段如下:

private fun VideoPlayer(videoId: String) {
    val context = LocalContext.current
    val exoPlayer = ExoPlayer.Builder(context).build()
    LaunchedEffect(videoId) {
        launch(Dispatchers.IO) {
            val effects = getEffects()
            val videoLink = getVideoStream(videoId)
            val mediaSource = MediaItem.fromUri(Uri.parse(videoLink))
            launch(Dispatchers.Main) {
                exoPlayer.setMediaItem(mediaSource)
                exoPlayer.setVideoEffects(effects)
                exoPlayer.prepare()
            }
        }
    }
    DisposableEffect(Unit) {
        onDispose { exoPlayer.release() }
    }
    AndroidView(
        factory = { ctx -> PlayerView(ctx).apply { player = exoPlayer } },
        modifier = Modifier
    )
}
private fun getEffects(): List<OverlayEffect> {
    val overlaysBuilder = ImmutableList.Builder<TextureOverlay>()
    val overlaySettings = OverlaySettings.Builder().setAlphaScale(1f).build()
    val textSizeSpan = RelativeSizeSpan(0.5f)
    val textColorSpan = ForegroundColorSpan(ContextCompat.getColor(this, R.color.white))
    val overlayText = SpannableString(memetextt)
    overlayText.setSpan(textSizeSpan, 0, overlayText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    overlayText.setSpan(textColorSpan, 0, overlayText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    overlayText.setSpan(TypefaceSpan("sans-serif-medium"), 0, overlayText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    val textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings)
    overlaysBuilder.add(textOverlay)
    val overlays = overlaysBuilder.build()
    val overlay = OverlayEffect(overlays)
    return listOf(overlay)
}
解决方案

Media3的TextOverlay本身没有直接支持自动换行的API,但可以通过两种方式实现文本换行:

方法1:预分割文本为多行

先根据视频宽度比例计算每行可容纳的字符数,手动将文本分割为多行后拼接成带换行符的SpannableString:

private fun getEffects(): List<OverlayEffect> {
    val overlaysBuilder = ImmutableList.Builder<TextureOverlay>()
    val overlaySettings = OverlaySettings.Builder().setAlphaScale(1f).build()
    val context = this
    
    // 配置文本参数
    val maxWidthRatio = 0.8f // 文本最大宽度占视频宽度的80%
    val textSizeRatio = 0.5f
    val paint = Paint().apply {
        textSize = resources.displayMetrics.heightPixels * textSizeRatio
        typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
    }
    
    // 分割文本为多行
    val maxTextWidth = resources.displayMetrics.widthPixels * maxWidthRatio
    val splitText = splitTextToLines(memetextt, paint, maxTextWidth)
    val overlayText = SpannableString(splitText)
    
    // 应用文本样式
    val textSizeSpan = RelativeSizeSpan(textSizeRatio)
    val textColorSpan = ForegroundColorSpan(ContextCompat.getColor(context, R.color.white))
    val typefaceSpan = TypefaceSpan("sans-serif-medium")
    overlayText.setSpan(textSizeSpan, 0, overlayText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    overlayText.setSpan(textColorSpan, 0, overlayText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    overlayText.setSpan(typefaceSpan, 0, overlayText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    
    val textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings)
    overlaysBuilder.add(textOverlay)
    return listOf(OverlayEffect(overlaysBuilder.build()))
}

// 辅助方法:按指定宽度分割文本为多行
private fun splitTextToLines(text: String, paint: Paint, maxWidth: Float): String {
    val sb = StringBuilder()
    val words = text.split(" ")
    var currentLine = StringBuilder()
    
    for (word in words) {
        val testLine = if (currentLine.isEmpty()) word else "${currentLine} $word"
        val lineWidth = paint.measureText(testLine)
        
        if (lineWidth <= maxWidth) {
            currentLine.append(if (currentLine.isEmpty()) "" else " ").append(word)
        } else {
            sb.append(currentLine).append("\n")
            currentLine = StringBuilder(word)
        }
    }
    if (currentLine.isNotEmpty()) {
        sb.append(currentLine)
    }
    return sb.toString()
}

方法2:自定义TextureOverlay绘制

如果需要更灵活的换行控制(比如自动换行、文本对齐),可以自定义TextureOverlay,在drawFrame方法中手动绘制多行文本:

class CustomTextOverlay(
    private val text: String,
    private val textSizeRatio: Float,
    private val textColor: Int,
    overlaySettings: OverlaySettings
) : TextureOverlay(overlaySettings) {

    private val paint = Paint().apply {
        color = textColor
        typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
        textAlign = Paint.Align.CENTER
    }

    override fun drawFrame(
        canvas: Canvas,
        presentationTimeUs: Long,
        videoWidth: Int,
        videoHeight: Int
    ) {
        super.drawFrame(canvas, presentationTimeUs, videoWidth, videoHeight)
        // 基于视频高度设置文本大小
        paint.textSize = videoHeight * textSizeRatio
        // 设置文本最大宽度(占视频宽度的80%)
        val maxTextWidth = videoWidth * 0.8f
        
        // 创建多行文本布局
        val textLayout = StaticLayout.Builder
            .obtain(text, 0, text.length, paint, maxTextWidth.toInt())
            .setAlignment(Layout.Alignment.ALIGN_CENTER)
            .build()
        
        // 将文本绘制在视频中央
        val x = videoWidth / 2f
        val y = (videoHeight - textLayout.height) / 2f
        canvas.save()
        canvas.translate(x, y)
        textLayout.draw(canvas)
        canvas.restore()
    }
}

// 使用自定义Overlay
private fun getEffects(): List<OverlayEffect> {
    val overlaysBuilder = ImmutableList.Builder<TextureOverlay>()
    val overlaySettings = OverlaySettings.Builder().setAlphaScale(1f).build()
    val customOverlay = CustomTextOverlay(
        text = memetextt,
        textSizeRatio = 0.5f,
        textColor = ContextCompat.getColor(this, R.color.white),
        overlaySettings = overlaySettings
    )
    overlaysBuilder.add(customOverlay)
    return listOf(OverlayEffect(overlaysBuilder.build()))
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 07:53:15