Compose笔记(十九)--NestedScroll
这一节了解一下Compose中的NestedScroll的使用,NestedScroll 在 Jetpack Compose 里主要用于处理嵌套滚动的场景,也就是当一个可滚动组件嵌套在另一个可滚动组件内部时,能让它们之间的滚动操作协同工作。
API
1. Modifier.nestedScroll()
含义:
为可滚动组件(如 LazyColumn、Box 等)添加嵌套滚动支持,使其能够与其他滚动组件交互。
作用:1)注册嵌套滚动连接:将自定义的 NestedScrollConnection 绑定到组件,定义滚动事件的分发逻辑。 2)协调父子滚动:通过 NestedScrollDispatcher 传递滚动增量(Offset)或抛掷速度(Velocity),实现父子组件的滚动同步或优先级控制。
参数:
connection:实现 NestedScrollConnection的实例,定义滚动事件的响应逻辑。
dispatcher:用于触发嵌套滚动周期(通常由可滚动组件内部提供,无需手动传递)。
2. NestedScrollConnection
含义:定义组件在嵌套滚动事件中的行为,包含四个核心回调方法,分别处理滚动前、滚动后、抛掷前、抛掷后的逻辑。
作用:1)控制滚动分配:决定父组件是否消费部分或全部滚动增量,避免滚动冲突。 2)实现自定义交互:例如折叠工具栏时,优先滚动顶部栏而非内容区域。
回调方法:
onPreScroll(available: Offset, source: NestedScrollSource): Offset
触发条件:子组件滚动前,父组件可优先消费滚动增量。
返回值:父组件实际消费的增量(Offset),未消费部分会传递给子组件。
用途:实现折叠工具栏时,优先折叠顶部栏。
onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset
触发条件:子组件滚动后,父组件可响应剩余滚动增量。
返回值:父组件额外消费的增量(通常用于补充滚动)。
用途:子组件滚动到边界后,父组件继续滚动。
onPreFling(available: Velocity): Velocity
触发条件:子组件抛掷(快速滑动)前,父组件可优先消费抛掷速度。
返回值:父组件实际消费的速度(Velocity),未消费部分会传递给子组件。
onPostFling(consumed: Velocity, available: Velocity): Velocity
触发条件:子组件抛掷后,父组件可响应剩余抛掷速度。
返回值:父组件额外消费的速度(通常用于补充抛掷)。
栗子:
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.verticalScroll
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.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.rememberScrollState@Composable
fun NestedScrollExample() {// 创建一个嵌套滚动连接val nestedScrollConnection = object : NestedScrollConnection {override fun onPreScroll(available: androidx.compose.ui.geometry.Offset, source: NestedScrollSource): androidx.compose.ui.geometry.Offset {return if (source == NestedScrollSource.Drag && available.y != 0f) {androidx.compose.ui.geometry.Offset(0f, available.y)} else {androidx.compose.ui.geometry.Offset.Zero}}override fun onPostScroll(consumed: androidx.compose.ui.geometry.Offset,available: androidx.compose.ui.geometry.Offset,source: NestedScrollSource): androidx.compose.ui.geometry.Offset {return androidx.compose.ui.geometry.Offset.Zero}}Column(modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection).verticalScroll(rememberScrollState())) {repeat(20) {Text(text = "Vertical Item $it",modifier = Modifier.fillMaxWidth().padding(16.dp).background(Color.LightGray))}Row(modifier = Modifier.fillMaxWidth().height(200.dp).horizontalScroll(rememberScrollState())) {repeat(20) {Text(text = "Horizontal Item $it",modifier = Modifier.padding(16.dp).background(Color.Green))}}}
}
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
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.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.lazy.rememberLazyListState@Composable
fun NestedScrollExampleTest() {val headerHeight = 200.dpvar headerOffset by remember { mutableStateOf(0.dp) }val lazyListState = rememberLazyListState()val headerOffsetAnimatable = remember { Animatable(0f) }val nestedScrollConnection = remember {object : NestedScrollConnection {override fun onPreScroll(available: androidx.compose.ui.geometry.Offset, source: NestedScrollSource): androidx.compose.ui.geometry.Offset {val delta = available.yval newOffset = (headerOffset.value + delta).coerceIn(-headerHeight.value, 0f)headerOffset = newOffset.dpreturn if (delta > 0 && headerOffset.value < 0) {androidx.compose.ui.geometry.Offset(0f, -delta)} else {androidx.compose.ui.geometry.Offset.Zero}}override fun onPostScroll(consumed: androidx.compose.ui.geometry.Offset,available: androidx.compose.ui.geometry.Offset,source: NestedScrollSource): androidx.compose.ui.geometry.Offset {return androidx.compose.ui.geometry.Offset.Zero}}}LaunchedEffect(headerOffset) {headerOffsetAnimatable.animateTo(targetValue = headerOffset.value,animationSpec = tween(durationMillis = 200))}Column(modifier = Modifier.fillMaxSize().nestedScroll(nestedScrollConnection)) {Box(modifier = Modifier.fillMaxWidth().height(headerHeight).offset(y = headerOffsetAnimatable.value.dp).background(Color.Blue)) {Text(text = "Header",modifier = Modifier.padding(16.dp),color = Color.White)}LazyColumn(state = lazyListState,modifier = Modifier.fillMaxSize()) {items(100) { index ->Text(text = "Item $index",modifier = Modifier.fillMaxWidth().padding(16.dp))}}}
}
注意:
1 顺序问题:在使用多个修饰符时,nestedScroll 修饰符的位置可能会影响滚动行为。一般建议将其放在靠近 scroll 修饰符的位置,以确保嵌套滚动逻辑正确执行。
2 避免过度嵌套:过多的嵌套滚动组件会增加滚动处理的复杂度,降低性能。要尽量减少嵌套层级,保持滚动逻辑的简洁性。
3 保存和恢复滚动状态:在配置更改(如屏幕旋转)时,要确保嵌套滚动的状态能够正确保存和恢复。可以使用 rememberSaveable 来保存关键的滚动状态。