这一节主要了解一下Compose中onVisibilityChanged,它是用来监听组件可见状态变化,简单总结如下:
API onVisibilityChanged:拿到组件与视口的窗口坐标矩形,求矩形交集,计算重叠面积占组件自身总面积的比值
minDurationMs:可见状态持续防抖时间
minFractionVisible:最小可见面积占比阈值,默认 1.0
viewportBounds:自定义视口容器,可为 null。
callback:可见状态回调函数
场景:
1 列表曝光统计
2 组件自动播放控制
3 动画触发
栗子:
Kotlin
mport android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onVisibilityChanged
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@Composable
fun VisiblitySample() {
val list = (1..15).map { "Item $it" }
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(list) { itemText ->
var logStr = "未曝光"
Box(modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.padding(8.dp)
.onVisibilityChanged(
minDurationMs = 0,
minFractionVisible = 0.5f
) { isVisible ->
logStr = if (isVisible) "已曝光 $itemText" else "离开视口 $itemText"
println(logStr)
}
) {
Text(
text = "$itemText \n :50%可见触发",
modifier = Modifier.padding(12.dp),
textAlign = TextAlign.Center
)
}
}
}
}
Kotlin
import android.util.Log
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onVisibilityChanged
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
data class Product(
val id: String,
val name: String,
val price: Double
)
@Composable
fun FeedSample() {
val products = remember {
List(30) { Product(id = "SKU_$it", name = "高级商品 $it", price = 99.9 * it) }
}
val exposedMap = remember { mutableStateMapOf<String, Boolean>() }
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(
items = products,
key = { it.id }
) { product ->
val isAlreadyExposed = exposedMap[product.id] == true
ProductCard(
product = product,
isExposed = isAlreadyExposed,
onExpose = {
exposedMap[product.id] = true
Log.d("EcommerceFeed", "[埋点上报] 商品有效曝光: ${product.name} (价格: ${product.price})")
}
)
}
}
}
@Composable
fun ProductCard(
product: Product,
isExposed: Boolean,
onExpose: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.onVisibilityChanged(
minDurationMs = 500,
minFractionVisible = 0.6f
) { isVisible ->
if (isVisible && !isExposed) {
onExpose()
}
}
) {
Box(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Column(modifier = Modifier.align(Alignment.CenterStart)) {
Text(text = product.name, fontSize = 18.sp)
Text(text = "¥ ${product.price}", fontSize = 14.sp, color = Color.Red)
}
if (isExposed) {
Text(
text = " 已曝光",
modifier = Modifier.align(Alignment.CenterEnd),
color = Color.Gray
)
}
}
}
}