如何在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
相关产品推荐
相关产品推荐

