插一下,怎样在 composable 函数中获取情况栏高度
val statusBarHeightDp = with(LocalDensity.current) {
WindowInsets.Companion.statusBars.getTop(this).toDp()
}
// 打印 WindowInsets.Companion.statusBars 结果是:
// statusBars(0, 136, 0, 0)
// AndroidWindowInsets toString() 方法完结是(左上右下)
// return "$name(${insets.left}, ${insets.top}, ${insets.right},${insets.bottom})"
完结折叠/掩盖布局
NetedScrollConnection
Compose 中默许支撑嵌套翻滚。
父容器增加 Modifier.nestedScroll(connection) 后子组件发生翻滚工作后会先通过 NetedScrollConnection 的 onPreScroll() 、 onPostScroll() 或 onPreFling() 、onPostFling() 方法。
这些方法的回来值代表 connection 消耗了多少工作值,剩下的工作值会持续交给子组件处理。假如 connection 悉数工作值,子组件就不会触发翻滚工作。
NetedScrollConnection 相关内容参考
NestedScrollConnection
nestedScroll
Jetpack compose 仿QQ音乐完结下拉改写上拉加载更多
我们这儿只用到 onPreScroll()
/*
available 可用的 工作值
source 工作来源 Drag / Fling
回来 connect 消耗的 Offset , 默许 Offset.Zero 既不消耗
*/
fun onPreScroll(available: Offset, source: NestedScrollSource): Offset = Offset.Zero
布局结构和终究效果
两种布局结构如下
|-布局容器(modifier = Modifier.nestedScroll(connection))
|-Top容器
|-Bottom容器(modifier = Modifier.fillMaxSize().scrollable(rememberScrollState(), Orientation.Vertical))
都运用 NestedScrollConnection 完结,Bottom 容器发生滑动工作后运用 NestedScrollConnection 阻挠,根据工作值对 Top 或 Bottom 容器做出相应改动。
效果如下
折叠布局
掩盖布局
NetedScrollConnection 完结
首先在 connect 的 onPreScroll() 方法中判别是否让 connect 消耗掉工作值。
定义 TopStates 来辅佐判别
enum class TopStates {
EXPANDED,// 默许翻开情况
SCROLLING,//中心情况 ,中心情况时 connection 也要消耗掉悉数工作
COLLAPSED// 折叠情况
}
将判别方法封装到 CollapsableLayoutState 中
class CollapsableLayoutState{
fun shouldConsumeAvailable(available: Offset): Boolean {
//省掉
}
}
还需求考虑到 bottom 容器中假如有能够翻滚的子组件发生翻滚的情况
这种情况下 connection 不应该消耗工作
class CollapsableScrollConnection(
private val isChildScrolled: State<Boolean> = mutableStateOf(false),
private val state: CollapsableLayoutState
) : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (isChildScrolled.value) return Offset.Zero
if (state.shouldConsumeAvailable(available)) {
state.plusOffset(available)
return available
}
return Offset.Zero
}
}
需求消耗工作的情况下,我们将改动记录在 CollapsableLayoutState 的offsetState 中,详细怎样处理不在 connection 中完结。
CollapsableLayoutState
先完结上面说到的内容
class CollapsableLayoutState{
private val offsetState: MutableState<Offset> = mutableStateOf(Offset.Zero)
private val topState: MutableState<TopStates> = mutableStateOf(TopStates.EXPANDED)
fun plusOffset(offset: Offset) {
offsetState.value = offsetState.value + offset
}
fun shouldConsumeAvailable(available: Offset): Boolean {
return topState.value == TopStates.SCROLLING //处于折叠和翻开情况
//翻开情况 向上滑动
|| topState.value == TopStates.EXPANDED && available.y < 0
//折叠情况 向下滑动
|| topState.value == TopStates.COLLAPSED && available.y > 0
}
}
这两个布局效果的完结就是根据 offsetState 值的改动来改动 top 容器或 bottom 容器的高度。
- 这个改动的最大值是top 容器的高度 ,最小值之能够通过传参来指定。
- 核算改动,根据核算出的差值设置其时 topState.value 的值
- 核算出差值在 [最小值,最大值] 中百分比 供给一个跟从改动的 [0,1] 的 state 供外部监听完结动画
@Composable
fun rememberCollapsableLayoutState(minTopHeightDp: Dp = 0.dp): CollapsableLayoutState {
val density = LocalDensity.current
return remember(minTopHeightDp,density) {
CollapsableLayoutState(minTopHeightDp, density)
}
}
class CollapsableLayoutState(
private val minTopHeightDp: Dp,
density: Density
){
private val offsetState: MutableState<Offset> = mutableStateOf(Offset.Zero)
private val topState: MutableState<TopStates> = mutableStateOf(TopStates.EXPANDED)
//top 最大高度需求 top 容器通过丈量后得到
private val maxHeightState: MutableState<Int> = mutableStateOf(-1)
//默许 EXPANDED 情况 ,progress 默许为 1
private val expendProgressState = mutableStateOf(1f)
val currentTopState
get() = topState.value
val minTopHeightPx = with(density) {
minTopHeightDp.toPx().toInt()
}
val maxTopHeightPx
get() = maxHeightState.value
val maxOffsetY
get() = maxTopHeightPx - minTopHeightPx
val expendProgress
get() = expendProgressState.value
fun shouldConsumeAvailable(available: Offset): Boolean {
return topState.value == TopStates.SCROLLING //处于折叠和翻开情况
//翻开情况 向上滑动
|| topState.value == TopStates.EXPANDED && available.y < 0
//折叠情况 向下滑动
|| topState.value == TopStates.COLLAPSED && available.y > 0
}
fun plusOffset(offset: Offset) {
offsetState.value = offsetState.value + offset
}
/**
* 设置 top 容器最大高度
* @param maxHeight Int
*/
fun updateMaxTopHeight(maxHeight: Int) {
if (maxHeight == maxHeightState.value) return
maxHeightState.value = maxHeight
}
/*
根据 offsetState 核算其时 top 容器的高度
*/
fun calcTopHeight():Int{
val curTopHeight = (maxTopHeightPx + offsetState.value.y.toInt()).coerceIn(minTopHeightPx,maxTopHeightPx)
//根据 curTopHeight 设置 LayoutState ,核算 expendProgressState
when (curTopHeight) {
minTopHeightPx -> {
offsetState.value = Offset(0f, -maxOffsetY.toFloat())
expendProgressState.value = 0f
updateLayoutState(TopStates.COLLAPSED)
}
maxTopHeightPx -> {
offsetState.value = Offset.Zero
expendProgressState.value = 1f
updateLayoutState(TopStates.EXPANDED)
}
else -> {
val offsetY = (maxTopHeightPx - curTopHeight).toFloat()
expendProgressState.value = 1 - offsetY / maxOffsetY
updateLayoutState(TopStates.SCROLLING)
}
}
return curTopHeight
}
private fun updateLayoutState(state:TopStates){
if (state == topState.value) return
topState.value = state
}
}
折叠布局完结
运用 Column 最为父容器, 根据 CollapsableLayoutState 核算后 top 的高度来改动 top 容器的大小 , bottom 容器在 fillMaxSize 的情况下也会随之改动。
@Composable
fun CollapsableLayout(
topContent: @Composable () -> Unit,
bottomContent: @Composable () -> Unit,
bottomContentScrolled: State<Boolean> = mutableStateOf(false),
state: CollapsableLayoutState = rememberCollapsableLayoutState(0.dp)
) {
val connection: CollapsableScrollConnection = remember {
CollapsableScrollConnection(bottomContentScrolled, state)
}
val heightModifier = if (state.maxTopHeightPx != -1) {
Modifier.height(with(LocalDensity.current){
state.calcTopHeight().toDp()
})
} else {
Modifier
}
Column(modifier = Modifier
.nestedScroll(connection)
) {
Box(
modifier = Modifier.then(heightModifier)
.onSizeChanged {
//设置 top 最大高度
if (state.maxTopHeightPx == -1) {
state.updateMaxTopHeight(it.height)
}
}
) { topContent() }
Box(
modifier = Modifier.fillMaxSize()
.scrollable(rememberScrollState(), Orientation.Vertical)
) { bottomContent() }
}
}
掩盖布局完结
运用 Layout 做为父容器,完结 MeasurePolicy , 根据 CollapsableLayoutState 核算后 top 的高度来对 bottom 容器进行测绘和布局到达掩盖的效果
@Composable
fun CoverLayout(
topContent: @Composable BoxScope.()-> Unit,
bottomContent: @Composable BoxScope.() -> Unit,
bottomContentScrolled: State<Boolean> = mutableStateOf(false),
state:CollapsableLayoutState = rememberCollapsableLayoutState()
) {
val connection: CollapsableScrollConnection = remember {
CollapsableScrollConnection(bottomContentScrolled, state)
}
Layout(modifier = Modifier
.nestedScroll(connection), content = {
Box(content = topContent )
Box(
modifier = Modifier
.fillMaxSize()
.scrollable(rememberScrollState(), Orientation.Vertical)
.background(Color.Green),
content = bottomContent
)
}) { measurables, constraints ->
val placeableTop = measurables[0].measure(constraints)
if (state.maxTopHeightPx == -1){
state.updateMaxTopHeight(placeableTop.height)
}
val topHeight = state.calcTopHeight()
//bottom 的最大高度约束要减去 top 容器的高度
val bottomConstraints =
constraints.copy(maxHeight = constraints.maxHeight - topHeight)
val placeableBottom = measurables[1].measure(bottomConstraints)
layout(constraints.maxWidth, constraints.maxHeight) {
placeableTop.placeRelative(0, 0)
//bottom 容器放在 top 之下
placeableBottom.placeRelative(0, topHeight)
}
}
}
运用
@Composable
fun Test() {
val listState = rememberLazyListState()
// bottom 中 LazyColumn 内容是否翻滚过
val bottomContentScrolled: State<Boolean> = remember {
derivedStateOf {
!(listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0)
}
}
val collapsableLayoutState = rememberCollapsableLayoutState()
CollapsableLayout(
topContent = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
//根据 expendProgress 设置 alpha 动画效果
.alpha(collapsableLayoutState.expendProgress)
){
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.c19e2e81da3ede74c24c29bf6b1a800b),
contentScale = ContentScale.FillBounds,
contentDescription =""
)
}
},
bottomContent = {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding(),
state = listState,
verticalArrangement = Arrangement.spacedBy(22.dp)
) {
items(30) {
Text(text = "第 $it 项")
}
}
},
bottomContentScrolled = bottomContentScrolled,
state = collapsableLayoutState
)
}