Skip to content

Commit

Permalink
Merge pull request onebone#31 from onebone/feat/scaffold-align
Browse files Browse the repository at this point in the history
Support placing child at fixed position regardless of scroll state
  • Loading branch information
onebone authored Mar 12, 2022
2 parents f968d73 + 8ee35f3 commit b77ac97
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 7 deletions.
11 changes: 11 additions & 0 deletions app/src/main/java/me/onebone/toolbar/ParallaxActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
Expand Down Expand Up @@ -120,6 +121,16 @@ fun ParallaxEffect() {
)
}
}

@OptIn(ExperimentalToolbarApi::class)
Button(
modifier = Modifier
.padding(16.dp)
.align(Alignment.BottomEnd),
onClick = { }
) {
Text(text = "Floating Button!")
}
}

Row(
Expand Down
62 changes: 56 additions & 6 deletions lib/src/main/java/me/onebone/toolbar/CollapsingToolbarScaffold.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import kotlin.math.max

@Stable
Expand Down Expand Up @@ -70,6 +75,11 @@ fun rememberCollapsingToolbarScaffoldState(
}
}

interface CollapsingToolbarScaffoldScope {
@ExperimentalToolbarApi
fun Modifier.align(alignment: Alignment): Modifier
}

@Composable
fun CollapsingToolbarScaffold(
modifier: Modifier,
Expand All @@ -78,7 +88,7 @@ fun CollapsingToolbarScaffold(
enabled: Boolean = true,
toolbarModifier: Modifier = Modifier,
toolbar: @Composable CollapsingToolbarScope.() -> Unit,
body: @Composable () -> Unit
body: @Composable CollapsingToolbarScaffoldScope.() -> Unit
) {
val flingBehavior = ScrollableDefaults.flingBehavior()

Expand All @@ -96,7 +106,8 @@ fun CollapsingToolbarScaffold(
) {
toolbar()
}
body()

CollapsingToolbarScaffoldScopeInstance.body()
},
modifier = modifier
.then(
Expand All @@ -107,6 +118,10 @@ fun CollapsingToolbarScaffold(
}
)
) { measurables, constraints ->
check(measurables.size >= 2) {
"the number of children should be at least 2: toolbar, (at least one) body"
}

val toolbarConstraints = constraints.copy(
minWidth = 0,
minHeight = 0
Expand All @@ -124,8 +139,14 @@ fun CollapsingToolbarScaffold(
)

val toolbarPlaceable = measurables[0].measure(toolbarConstraints)
val bodyPlaceables =
measurables.drop(1).map { it.measure(bodyConstraints) }

val bodyMeasurables = measurables.subList(1, measurables.size)
val childrenAlignments = bodyMeasurables.mapTo(ArrayList(bodyMeasurables.size)) {
(it.parentData as? ScaffoldParentData)?.alignment
}
val bodyPlaceables = bodyMeasurables.mapTo(ArrayList(bodyMeasurables.size)) {
it.measure(bodyConstraints)
}

val toolbarHeight = toolbarPlaceable.height

Expand All @@ -139,10 +160,39 @@ fun CollapsingToolbarScaffold(
).coerceIn(constraints.minHeight, constraints.maxHeight)

layout(width, height) {
bodyPlaceables.forEach {
it.place(0, toolbarHeight + state.offsetY)
bodyPlaceables.forEachIndexed { index, placeable ->
val alignment = childrenAlignments[index]

if (alignment == null) {
placeable.place(0, toolbarHeight + state.offsetY)
} else {
val offset = alignment.align(
size = IntSize(placeable.width, placeable.height),
space = IntSize(width, height),
layoutDirection = LayoutDirection.Ltr
)
placeable.place(offset)
}
}
toolbarPlaceable.place(0, state.offsetY)
}
}
}

internal object CollapsingToolbarScaffoldScopeInstance: CollapsingToolbarScaffoldScope {
@ExperimentalToolbarApi
override fun Modifier.align(alignment: Alignment): Modifier =
this.then(ScaffoldChildAlignmentModifier(alignment))
}

private class ScaffoldChildAlignmentModifier(
private val alignment: Alignment
) : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any {
return (parentData as? ScaffoldParentData) ?: ScaffoldParentData(alignment)
}
}

private data class ScaffoldParentData(
var alignment: Alignment? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fun ToolbarWithFabScaffold(
toolbar: @Composable CollapsingToolbarScope.() -> Unit,
fab: @Composable () -> Unit,
fabPosition: FabPosition = FabPosition.End,
body: @Composable () -> Unit
body: @Composable CollapsingToolbarScaffoldScope.() -> Unit
) {
SubcomposeLayout(
modifier = modifier
Expand Down

0 comments on commit b77ac97

Please sign in to comment.