视频特效动画制作,广告中占据了大多数,对于程序员来讲,费时费力的事情。
用Compose写出动画,然后录屏成视频转化后的GIF效果,本身是带
音乐背景 的。
先来看效果图GIF一
:

效果图GIF二:
哪吒2魔童脑海已经冲到了全球票房 第5了

一、前言
广告视频作为现代营销的核心工具,其意义远超传统广告形式,通过视听结合的方式,能够更高效地传递信息、激发情感共鸣并推动商业目标实现。
本文来介绍最简单的自动横向滚动广告视频动画用Compose怎么制作出来。
- 制作数据模型,可扩展,可自定义,
- 每一个
Item的compose
可扩展自定义。 - 涉及到
compose 的 Column ,LazyRow ,rememberLazyListState(),snapshotFlow
的用法 - 涉及到
compose 的 LaunchedEffect,DisposableEffect
的用法 - 涉及到
compose 中ExoPlayer
怎么播放音乐 - 涉及到
compose 中 Glide
怎么去给视图加载背景图片
二、数据模型设计
ScrollViewModel
:滚动模型数据, 其中:
title
:广告标题
showCount
:屏幕可见个数,设置为0没有开始那个动画
list
:数据集
bgImg
:背景图片
musicUrl
:音乐文件地址
itemWidth
:屏幕显示个数的单个UI宽度
isPlayComplete
:是否滚动到头了,控制音乐播放结束
tweenTimeL
:前几个单个动画执行动画时间
colorNo
:排名序号颜色
noFontSize
:排名序号字体大小
colorTitle
:单个标题颜色
titleFontSize
:单个标题字体大小
colorValue
:单个值颜色 valueFontSize
:单个值字体大小
kotlin
data class ScrollViewModel(
val title: String, //标题
val showCount: Int, //UI宽度下刚好显示个数
val list: MutableList<out TopDataSource>,//数据
val bgImg: String = "", //背景图片
val musicUrl: String = "" //背景音乐
) {
var itemWidth = 0 //屏幕显示个数的单个UI宽度
var isPlayComplete = false //是否滚动到头了,控制音乐播放结束
var tweenTimeL = 3000L //前几个单个动画执行动画时间
@Stable
var colorNo = Color.Yellow //排名序号颜色
var noFontSize = 23 //排名序号字体大小
@Stable
var colorTitle = Color.White //单个标题颜色
var titleFontSize = 18 //单个标题字体大小
@Stable
var colorValue = Color.White //单个值颜色
var valueFontSize = 22 //单个值字体大小
val tweenTimeI: Int
get() = tweenTimeL.toInt()
var formatString: String = ""//数字格式化设置
var multiplier: Float = 1f//数据显示格式所用的乘数
fun getTextValueFormat(value: Float): String {
return formatString.format(value * multiplier)
}
}
数据集属性,open式,可以扩展:
title
:单条数据标题
img
: 单条数据图片地址
value
:数据值
kotlin
open class TopDataSource(
val title: String, //单条数据标题
val img: String, //单条数据图片地址
val value: Float //数据值
)
三、真正使用
1、repositories
中添加如下maven
rust
repositories {
maven { url 'https://repo1.maven.org/maven2/' }
maven { url 'https://s01.oss.sonatype.org/content/repositories/releases/' }
}
}
2、 dependencies
中添加依赖
scss
implementation("io.github.wgllss:Wgllss-Scroll-View:1.0.02")
3、ViewModel
中使用 准备好数据,和所有数据模型配置,都在里面
less
class DhListViewModel : ViewModel() {
private val _datas = MutableLiveData<ScrollViewModel>()
val datas: LiveData<ScrollViewModel> = _datas
fun add() {
viewModelScope.launch {
val list = mutableListOf<TopDataSource>()
list.add(TopDataSource("阿凡达", "https://bkimg.cdn.bcebos.com/smart/6f061d950a7b02087bf48857e581e5d3572c11df6276-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 212.0f))
list.add(TopDataSource("复仇者联盟4:终局之战", "https://bkimg.cdn.bcebos.com/smart/2cf5e0fe9925bc31a5ffa7e950df8db1cb137025-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 202.99f))
list.add(TopDataSource("阿凡达:水之道", "https://bkimg.cdn.bcebos.com/smart/e7cd7b899e510fb30f24192fab6bdf95d143ad4ba649-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 168.25f))
list.add(TopDataSource("泰坦尼克号", "https://bkimg.cdn.bcebos.com/smart/f3d3572c11dfa9ec8a132753ae86e003918fa0ec6b62-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 164.23f))
list.add(TopDataSource("哪吒之魔童闹海", "https://bkimg.cdn.bcebos.com/smart/09fa513d269759ee3d6ddbeb3ea254166d224f4a71c3-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 155.05f))
list.add(TopDataSource("星球大战:原力觉醒", "https://bkimg.cdn.bcebos.com/smart/6c224f4a20a4462389da25bc9e22720e0df3d7d8-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 150.19f))
list.add(TopDataSource("复仇者联盟3:无限战争", "https://bkimg.cdn.bcebos.com/smart/d31b0ef41bd5ad6eddc41b61f2932edbb6fd536696ba-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 148.82f))
list.add(TopDataSource("蜘蛛侠:英雄无归", "https://bkimg.cdn.bcebos.com/smart/f3d3572c11dfa9ec8a13ddc33088e003918fa1ec6b94-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 141.6f))
list.add(TopDataSource("头脑特工队2", "https://bkimg.cdn.bcebos.com/smart/b3fb43166d224f4a20a4c53494af87529822720e7bd9-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 123.19f))
list.add(TopDataSource("侏罗纪世界", "https://bkimg.cdn.bcebos.com/smart/d62a6059252dd42a2834f25c70634cb5c9ea14ce1289-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 121.2f))
list.add(TopDataSource("狮子王", "https://bkimg.cdn.bcebos.com/smart/aa18972bd40735fae6cde368af0118b30f2443a7a298-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 120.51f))
list.add(TopDataSource("复仇者联盟", "https://bkimg.cdn.bcebos.com/smart/caef76094b36acaf24a1fd5772d98d1000e99cd3-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 110.26f))
list.add(TopDataSource("速度与激情7", "https://bkimg.cdn.bcebos.com/smart/5d6034a85edf8db1cb135ac97b7bca54564e9258de5c-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 109.88f))
list.add(TopDataSource("壮志凌云2:独行侠", "https://bkimg.cdn.bcebos.com/smart/9213b07eca8065380cd78040a988b644ad3459822411-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 108.45f))
list.add(TopDataSource("冰雪奇缘2", "https://bkimg.cdn.bcebos.com/smart/2e2eb9389b504fc2a7b68177eadde71191ef6dd2-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 105.41f))
list.add(TopDataSource("芭比", "https://bkimg.cdn.bcebos.com/smart/0e2442a7d933c895d143dc1db14464f082025aafaa04-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 104.93f))
list.add(TopDataSource("复仇者联盟2:奥创纪元", "https://bkimg.cdn.bcebos.com/smart/0df3d7ca7bcb0a469f9d6c986f63f6246a60afd0-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 101.88f))
list.add(TopDataSource("超级马力欧兄弟大电影", "https://bkimg.cdn.bcebos.com/smart/500fd9f9d72a6059252d735bb862239b033b5bb50c61-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 98.68f))
list.add(TopDataSource("黑豹", "https://bkimg.cdn.bcebos.com/smart/86d6277f9e2f0708283863716a7caf99a9014c085433-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 97.88f))
list.add(TopDataSource("哈利·波特与死亡圣器(下)", "https://bkimg.cdn.bcebos.com/smart/d0c8a786c9177f3ed1aacd6372cf3bc79e3d56c5-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 97.35f))
list.add(TopDataSource("死侍与金刚狼", "https://bkimg.cdn.bcebos.com/smart/0b46f21fbe096b63f624ec34aa6b9044ebf81a4c0604-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 97.02f))
list.add(TopDataSource("星球大战:最后的绝地武士", "https://bkimg.cdn.bcebos.com/smart/14ce36d3d539b6003af39b474806222ac65c113819be-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 96.76f))
list.add(TopDataSource("侏罗纪世界2", "https://bkimg.cdn.bcebos.com/smart/e824b899a9014c08ae6e4c53077b02087af4f4eb-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 95.02f))
list.add(TopDataSource("冰雪奇缘", "https://bkimg.cdn.bcebos.com/smart/a6efce1b9d16fdfaaf51f747c6d79b5494eef01fcd58-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 94.73f))
list.add(TopDataSource("钢铁侠3", "https://bkimg.cdn.bcebos.com/smart/b3fb43166d224f4a5bf0fa380bf790529822d1b4-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 91.81f))
list.add(TopDataSource("美女与野兽", "https://bkimg.cdn.bcebos.com/smart/3ac79f3df8dcd10043450c6f7b8b4710b8122fa7-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 91.81f))
list.add(TopDataSource("超人总动员2", "https://bkimg.cdn.bcebos.com/smart/e1fe9925bc315c6034a8bff32ce7dc1349540923d92a-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 90.15f))
list.add(TopDataSource("速度与激情8", "https://bkimg.cdn.bcebos.com/smart/203fb80e7bec54e736d1daa6cb608c504fc2d5623c48-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 89.62f))
list.add(TopDataSource("小黄人大眼萌", "https://bkimg.cdn.bcebos.com/smart/c2cec3fdfc03924562bbc7148194a4c27d1e259a-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 84.07f))
list.add(TopDataSource("美国队长3:内战", "https://bkimg.cdn.bcebos.com/smart/cc11728b4710b912bb7f8d69c4fdfc03934522ee-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 83.75f))
list.add(TopDataSource("海王", "https://bkimg.cdn.bcebos.com/smart/f7246b600c338744b135f9ce5c0fd9f9d62aa094-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 83.53f))
list.add(TopDataSource("指环王:王者无敌", "https://bkimg.cdn.bcebos.com/smart/503d269759ee3d6d55fbfbd691477a224f4a20a470d6-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 82.54f))
list.add(TopDataSource("蜘蛛侠:英雄远征", "https://bkimg.cdn.bcebos.com/smart/9922720e0cf3d7ca6fe915d9fc1fbe096b63a905-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 82.13f))
list.add(TopDataSource("惊奇队长", "https://bkimg.cdn.bcebos.com/smart/71cf3bc79f3df8dc9670d47ac011728b4610288f-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 82.04f))
list.add(TopDataSource("变形金刚3", "https://bkimg.cdn.bcebos.com/smart/dc54564e9258d109b3de7eaa7b11dbbf6c81800ae7db-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 81.49f))
list.add(TopDataSource("蝙蝠侠:黑暗骑士崛起", "https://bkimg.cdn.bcebos.com/smart/d0c8a786c9177f3e4f3b5b0f72cf3bc79f3d56a0-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 80.38f))
list.add(TopDataSource("007:大破天幕杀机", "https://bkimg.cdn.bcebos.com/smart/d6ca7bcb0a46f21fbe098d1eb3737c600c338744007c-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 80.38f))
list.add(TopDataSource("变形金刚4:绝迹重生", "https://bkimg.cdn.bcebos.com/smart/2e2eb9389b504fc28c79a73fefdde71190ef6de1-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 80.14f))
list.add(TopDataSource("侏罗纪公园", "https://bkimg.cdn.bcebos.com/smart/7acb0a46f21fbe098007a2fe6d600c338644adfb-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 80.08f))
list.add(TopDataSource("小丑", "https://bkimg.cdn.bcebos.com/smart/9d82d158ccbf6c81800ad21d7a77a63533fa838beaa6-bkimg-process,v_1,rw_1,rh_1,maxl_216,pad_1,color_ffffff?x-bce-process=image/format,f_auto", 78.23f))
// list.sortByDescending { it.value }
list.sortBy { it.value }
val showCount = 3
val model = ScrollViewModel(
"全球影史票房榜TOP40", showCount, list, "https://img1.baidu.com/it/u=452832960,881910017&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=281",//背景图片
musicUrl = "asset:///vv.mp3" //背景音乐,可配置网络链接
).apply {
itemWidth = DisplayUtil.getSreenWidth(MyApp.application) / showCount
formatString = "%.2f亿" //数字格式化设置
multiplier = 1f //数据显示格式所用的乘数
}
_datas.value = model
}
}
}
注意:showCount设置为0没有开始几个往下的动画,但是itemWidth必须要设置宽度
,那儿单独设置屏幕显示个数
4、Compose
页面调用方
- 使用我默认实现的
Item 布局
:HorizontalScroll(it, it.list)
,这样调用就行 - 自定义
Item布局
,可监听滚动到最右边,可自定义item的compose
kotlin
HorizontalScroll(it, it.list, onPlayComplete = {
//滚动到右边,背景音乐播放结束
}) { data: TopDataSource, index: Int, size: Int ->
//真正itemView 布局compose,可自定义
RealItemView(data, it.itemWidth, index, size, it)
}
- 顶部gif效果图示例调用如下:
kotlin
@Composable
fun horizontalScrollView(viewModel: DhListViewModel) {
val datas by viewModel.datas.observeAsState()
datas?.let {
HorizontalScroll(it, it.list)
// HorizontalScroll(it, it.list, onPlayComplete = {
// //滚动到右边,背景音乐播放结束
// }) { data: TopDataSource, index: Int, size: Int ->
// //真正itemView 布局compose,可自定义
// RealItemView(data, it.itemWidth, index, size, it)
// }
}
}
四、动画真正实现逻辑
Compose实现整体逻辑比较简单
- 整个屏幕是一个
Column
,竖向的,里面包含一个标题Text
和一个横向LazyRow
LazyRow
通过listState = rememberLazyListState()
,listState.scrollBy(3.6f)
向右滚动:如下
scss
while (!isScrolledToLast) { // 无限循环,直到你决定停止它
delay(30) // 延迟时间,例如30毫秒滚动一次
scope.launch {
listState.scrollBy(3.6f)
}
}
- 通过
listState.layoutInfo
监听滚动到最右边:判断监听滚到右边逻辑:
firstVisibleIndex
:滚动时左边刚好显示的第一个
layoutInfo.visibleItemsInfo.size
:屏幕显示的个数,大多数情况下会比设置的多一个
layoutInfo.totalItemsCount
:视图里面数据集总共个数
这里滚动到最右边倒数第2个时后,再给到8s时间,让其滚动到最右边,这个时候认为滚动结束,停止背景音乐播放
ini
LaunchedEffect(remember { derivedStateOf { listState.layoutInfo } }) {
snapshotFlow { listState.firstVisibleItemIndex }.collect { firstVisibleIndex ->
val layoutInfo = listState.layoutInfo
val scrolledToLast = (firstVisibleIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount)
if (scrolledToLast) {
delay(8000L)
isScrolledToLast = true
it.isPlayComplete = true
player.stop()
player.release()
onPlayComplete?.invoke()
}
}
}
- 整体实现代码如下: 这里list内数据是TopDataSource的扩展泛型,可增加字段,真正item的compose可自定义布局,可自定义多家属性展示。
scss
@SuppressLint("UseKtx")
@Composable
fun <T : TopDataSource> HorizontalScroll(
it: ScrollViewModel, list: MutableList<T>, onPlayComplete: (() -> Unit)? = null, content: @Composable (data: T, index: Int, size: Int) -> Unit = { data: T, index: Int, size: Int ->
RealItemView(data, it.itemWidth, index, size, it)
}
) {
val context = LocalContext.current
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val size = remember(it) { list.size }
var isScrolledToLast by remember { mutableStateOf(false) }
var bitmapBg by remember { mutableStateOf(DefaultBitmapUtils.createImageBitmap()) }
var width by remember { mutableIntStateOf(0) }
var height by remember { mutableIntStateOf(0) }
val player = remember { ExoPlayer.Builder(context).build() }
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(remember { derivedStateOf { listState.layoutInfo } }) {
snapshotFlow { listState.firstVisibleItemIndex }.collect { firstVisibleIndex ->
val layoutInfo = listState.layoutInfo
val scrolledToLast = (firstVisibleIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount)
if (scrolledToLast) {
delay(8000L)
isScrolledToLast = true
it.isPlayComplete = true
player.stop()
player.release()
onPlayComplete?.invoke()
}
}
}
LaunchedEffect(Unit) {
Glide.with(context).asBitmap().load(it.bgImg).skipMemoryCache(false).diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
bitmapBg = resource.asImageBitmap()
}
})
player.run {
setMediaItem(MediaItem.fromUri(it.musicUrl.toUri()))
prepare()
play()
}
delay(1000L + (it.showCount * 3000L)) // 延迟时间,例如2秒滚动一次
while (!isScrolledToLast) { // 无限循环,直到你决定停止它
delay(30) // 延迟时间,例如30毫秒滚动一次
scope.launch {
listState.scrollBy(3.6f)
}
}
}
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_STOP) {
if (player.isPlaying) {
player.stop()
player.release()
}
if (!it.isPlayComplete) {
it.isPlayComplete = true
onPlayComplete?.invoke()
}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight()
.onSizeChanged {
width = it.width
height = it.height
}
.drawBehind {
drawImage(
bitmapBg, srcOffset = IntOffset.Zero, srcSize = IntSize(bitmapBg.width, bitmapBg.height), //绘制的图片大小
dstOffset = IntOffset.Zero, dstSize = IntSize(
width, height
)
)
}, horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.padding(0.dp, 5.dp, 0.dp, 0.dp), text = it.title, textAlign = TextAlign.Center, color = Color.White, fontSize = 22.sp
)
LazyRow(
state = listState, modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
itemsIndexed(list) { index, data ->
ItemView(index, it.showCount, data, size, it, content)
}
}
}
}
- 在
Item
里面,屏幕显示个数的第一屏,设置成动画,
这里使用到Compose
的AnimatedVisibility
动画,
配置好动画进入,进出时间slideInVertically(tween(it.tweenTimeI))
scss
@Composable
fun <T> ItemView(index: Int, showCount: Int, data: T, size: Int, it: ScrollViewModel, content: @Composable (data: T, index: Int, size: Int) -> Unit) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
if (showCount > index) {
delay(1000L + index * it.tweenTimeL)
visible = true
}
}
Box(
modifier = Modifier
.width(it.itemWidth.dp)
.wrapContentHeight()
) {
if (showCount > index) {
AnimatedVisibility(
visible = visible, enter = slideInVertically(tween(it.tweenTimeI)), exit = slideOutVertically(tween(it.tweenTimeI))
) {
content(data, index, size)
}
} else {
content(data, index, size)
}
}
}
五、总结
本文简单介绍了电影电视剧网红广告屏轮播介绍视频特效制作
- 制作数据模型,可扩展,
- 每一个
Item的compose
可扩展。 - 涉及到
compose 的 Column ,LazyRow ,rememberLazyListState(),snapshotFlow
的使用 - 涉及到
compose 的 LaunchedEffect,DisposableEffect
的使用 - 涉及到
compose 中ExoPlayer
怎么播放音乐 - 涉及到
compose 中 Glide
怎么去给视图加载背景图片。