前语
日常开发中我们不免遇到嵌套翻滚的需求,那么在Compose中又是怎么完结的呢?本文将用最简单的代码完结一个嵌套翻滚页面。
NestedScrollConnection
Compose中能够运用 nestedScroll 修饰符定义嵌套翻滚层次结构来行进灵活性。
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
return super.onPreScroll(available, source)
}
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset {
return super.onPostScroll(consumed, available, source)
}
override suspend fun onPreFling(available: Velocity): Velocity {
return super.onPreFling(available)
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
return super.onPostFling(consumed, available)
}
}
onPreScroll:预处理滑动工作,先交给父组件消费后再交由子组件 available:其时可用滑动偏移量 source:滑动类型 回来值:其时消费的滑动偏移量,假设不想消费可回来Offset.Zero
onPostScroll:子组件滑动后的回调 consumed:之前件消费滑动偏移量 available:其时剩下可用滑动偏移量 source:滑动工作的类型 回来值:其时消费的滑动偏移量,假设不想消费可回来Offset.Zero,则剩下偏移量会继续交由父组件进行处理
onPreFling 惯性翻滚工作预处理。 available:开始时的速度 回来值:其时组件消费的速度,假设不想消费可回来 Velocity.Zero
onPostFling 惯性翻滚工作处理 consumed:之前消费的一切速度 available:其时剩下可用的速度 回来值:其时组件消费的速度,假设不想消费可回来Velocity.Zero,则剩下速度会继续交由父组件进行处理。
接下来我们来完结下图作用:
我们先来完结布局,能够分为头部和列表伪代码如下:
Column{
Box{}
LazyColumn{}
}
接下来让头部和列表进行联动,我的主意是动态改动头部高度伪代码如下:
val titleBarSize = 45.dp
val targetHeight = 100.dp
val targetPercent by remember { mutableStateOf(1f) }
Column{
Box(
modifier = Modifier
.fillMaxWidth()
.height(titleBarSize + targetHeight * targetPercent.value)
){}
LazyColumn(
modifier = Modifier.fillMaxSize()
){}
}
NestedScrollConnection提供onPreScroll来预处理滑动工作,所以我们在此处理targetPercent的逻辑,伪代码如下:
val nestedScrollConnection = remember {
object : NestedScrollConnection {
var dyConsumed = 0f //记载消费距离
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.y // delta<0向上翻滚,反之向下翻滚
dyConsumed += delta
dyConsumed = dyConsumed.coerceAtMost(0f)
val percent = dyConsumed / targetHeightPx // 计算翻滚距离百分比
coroutineScope.launch {
targetPercent = 1 - abs(percent.coerceIn(-1f, 0f))
}
if (percent > -1 && percent < 0) { //向上翻滚时先交给父组件消费
return Offset(0f, delta)
}
return Offset.Zero
}
}
}
以上便是思路和核心代码,最终依照常规贴上完好代码:
@Composable
fun UserScreen(
userId: String,
onNavigateToLogin: () -> Unit = {},
onNavigateToSystem: (cid: String) -> Unit = {},
onNavigateToWeb: (url: String) -> Unit = {},
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val sw = context.getScreenWidth()
val titleBarSize = 45.dp
val titleBarSizePx = with(LocalDensity.current) { titleBarSize.roundToPx().toFloat() }
val avatarOffsetXPx = (sw - titleBarSizePx) / 2
val avatarOffsetX = Dp(context.px2dp(avatarOffsetXPx))
val targetHeight = 100.dp
val targetHeightPx = with(LocalDensity.current) { targetHeight.roundToPx().toFloat() }
val targetPercent by remember { mutableStateOf(Animatable(1f)) }
val nestedScrollConnection = remember {
object : NestedScrollConnection {
var dyConsumed = 0f
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = available.y
dyConsumed += delta
dyConsumed = dyConsumed.coerceAtMost(0f)
val percent = dyConsumed / targetHeightPx
coroutineScope.launch {
targetPercent.animateTo(1 - abs(percent.coerceIn(-1f, 0f)))
}
if (percent > -1 && percent < 0) {
return Offset(0f, delta)
}
return Offset.Zero
}
}
}
val viewModel: UserViewModel = viewModel(
factory = UserViewModel.provideFactory(userId)
)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.systemBarsPadding()
.nestedScroll(nestedScrollConnection)
) {
Box(
modifier = Modifier
.background(colorResource(R.color.theme))
.fillMaxWidth()
.height(titleBarSize + targetHeight * targetPercent.value)
) {
IconButton(
modifier = Modifier.height(titleBarSize),
onClick = {
if (context is AppCompatActivity) {
context.onBackPressedDispatcher.onBackPressed()
}
}
) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = null,
tint = colorResource(R.color.white)
)
}
Image(
painter = painterResource(id = uiState.coinResult.getAvatarId()),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(titleBarSize * targetPercent.value.coerceAtLeast(0.75f))
.align(Alignment.Center)
.offset(x = -(avatarOffsetX - titleBarSize) * (1 - targetPercent.value))
.clip(CircleShape),
)
Text(
text = uiState.coinResult.nickname,
modifier = Modifier
.align(Alignment.Center)
.offset(
x = -(avatarOffsetX - (titleBarSize * 2)) * (1 - targetPercent.value),
y = 35.dp * targetPercent.value
),
fontSize = 16.sp,
color = colorResource(R.color.text_fff),
)
Text(
text = "积分:${uiState.coinResult.coinCount}",
modifier = Modifier
.align(Alignment.Center)
.offset(x = 0.dp, y = 55.dp * targetPercent.value)
.graphicsLayer {
alpha = targetPercent.value
},
fontSize = 12.sp,
color = colorResource(R.color.text_fff),
)
}
BoxLayout(uiState.refreshing && !uiState.loading) {
SwipeRefresh(
modifier = Modifier
.fillMaxSize()
.background(colorResource(R.color.white)),
contentPadding = PaddingValues(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
refreshing = uiState.refreshing,
loading = uiState.loading,
onRefresh = { viewModel.getShareArticlesHome() },
onLoad = { viewModel.getShareArticlesNext() },
onRetry = { viewModel.getShareArticlesHome() },
data = uiState.articleResult,
) { _, item ->
ArticleCard(
item = item,
onNavigateToLogin = onNavigateToLogin,
onNavigateToSystem = onNavigateToSystem,
onNavigateToWeb = onNavigateToWeb
)
}
}
}
}
Thanks
以上便是本篇文章的全部内容,如有问题欢迎指出,我们一起行进。
假设觉得本篇文章对您有协助的话请点个赞让更多人看到吧,您的鼓动是我行进的动力。
谢谢~~
源代码地址
- UserScreen.kt miaowmiaow/fragmject