Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ abstract interface <#A: kotlin/Any?> com.flaringapp.compose.topbar.nestedscroll/
}

abstract interface com.flaringapp.compose.topbar.nestedcollapse/CollapsingTopBarColumnScope { // com.flaringapp.compose.topbar.nestedcollapse/CollapsingTopBarColumnScope|null[0]
abstract fun (androidx.compose.ui/Modifier).align(androidx.compose.ui/Alignment.Horizontal): androidx.compose.ui/Modifier // com.flaringapp.compose.topbar.nestedcollapse/CollapsingTopBarColumnScope.align|align@androidx.compose.ui.Modifier(androidx.compose.ui.Alignment.Horizontal){}[0]
abstract fun (androidx.compose.ui/Modifier).clipToCollapse(): androidx.compose.ui/Modifier // com.flaringapp.compose.topbar.nestedcollapse/CollapsingTopBarColumnScope.clipToCollapse|clipToCollapse@androidx.compose.ui.Modifier(){}[0]
abstract fun (androidx.compose.ui/Modifier).columnProgress(com.flaringapp.compose.topbar/CollapsingTopBarProgressListener): androidx.compose.ui/Modifier // com.flaringapp.compose.topbar.nestedcollapse/CollapsingTopBarColumnScope.columnProgress|columnProgress@androidx.compose.ui.Modifier(com.flaringapp.compose.topbar.CollapsingTopBarProgressListener){}[0]
abstract fun (androidx.compose.ui/Modifier).notCollapsible(): androidx.compose.ui/Modifier // com.flaringapp.compose.topbar.nestedcollapse/CollapsingTopBarColumnScope.notCollapsible|notCollapsible@androidx.compose.ui.Modifier(){}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
Expand Down Expand Up @@ -143,6 +144,8 @@ private class CollapsingTopBarColumnMeasurePolicy(
with(placer) {
place(
placeables = placeables,
layoutDirection = layoutDirection,
parentWidth = width,
topBarHeight = topBarHeightState,
totalHeight = totalHeight,
collapsibleHeight = collapsibleHeight,
Expand All @@ -155,6 +158,8 @@ private class CollapsingTopBarColumnMeasurePolicy(

fun Placeable.PlacementScope.place(
placeables: List<Placeable>,
layoutDirection: LayoutDirection,
parentWidth: Int,
topBarHeight: Float,
totalHeight: Int,
collapsibleHeight: Int,
Expand All @@ -165,6 +170,8 @@ private class CollapsingTopBarColumnMeasurePolicy(

override fun Placeable.PlacementScope.place(
placeables: List<Placeable>,
layoutDirection: LayoutDirection,
parentWidth: Int,
topBarHeight: Float,
totalHeight: Int,
collapsibleHeight: Int,
Expand All @@ -184,6 +191,11 @@ private class CollapsingTopBarColumnMeasurePolicy(
placementOffset = itemOffset

val parentData = placeable.columnParentData
val xPosition = (parentData?.alignment ?: Alignment.Start).align(
size = placeable.width,
space = parentWidth,
layoutDirection = layoutDirection,
)

// Pinned element
if (parentData?.isNotCollapsible == true) {
Expand All @@ -193,7 +205,7 @@ private class CollapsingTopBarColumnMeasurePolicy(
totalProgress = expandFraction,
itemProgress = 1f,
)
placeable.place(0, itemOffset - unhandledCollapseOffset, 1f)
placeable.place(xPosition, itemOffset - unhandledCollapseOffset, 1f)
return@forEach
}

Expand All @@ -209,7 +221,7 @@ private class CollapsingTopBarColumnMeasurePolicy(
if (parentData?.isNotCollapsible != true) {
parentData?.clipToCollapseHeightListener?.invoke(0)
}
placeable.place(0, itemOffset)
placeable.place(xPosition, itemOffset)
return@forEach
}

Expand All @@ -235,7 +247,7 @@ private class CollapsingTopBarColumnMeasurePolicy(
it - unhandledCollapseOffset
}

placeable.place(0, itemPositionWithOptionalPin)
placeable.place(xPosition, itemPositionWithOptionalPin)
}
}
}
Expand All @@ -244,6 +256,8 @@ private class CollapsingTopBarColumnMeasurePolicy(

override fun Placeable.PlacementScope.place(
placeables: List<Placeable>,
layoutDirection: LayoutDirection,
parentWidth: Int,
topBarHeight: Float,
totalHeight: Int,
collapsibleHeight: Int,
Expand Down Expand Up @@ -296,8 +310,13 @@ private class CollapsingTopBarColumnMeasurePolicy(
for (i in placeables.indices.reversed()) {
val placeable = placeables[i]
val yPosition = yPositions[i]
val xPosition = (placeable.columnParentData?.alignment ?: Alignment.Start).align(
size = placeable.width,
space = parentWidth,
layoutDirection = layoutDirection,
)

placeable.place(0, yPosition)
placeable.place(xPosition, yPosition)
}
}
}
Expand All @@ -316,21 +335,28 @@ public sealed class CollapsingTopBarColumnDirection {
public interface CollapsingTopBarColumnScope {

/**
* Registers a progress listener to be notified every time top bar column collapse height
* changes. Only the last modifier in chain takes effect.
* Align the element horizontally within the width of [CollapsingTopBarColumn].
* Only the last modifier in chain takes effect.
*
* @param listener the listener that gets notified of every collapse progress update.
*
* @see CollapsingTopBarProgressListener
* @param alignment the horizontal alignment of the element inside the column.
*/
public fun Modifier.columnProgress(listener: CollapsingTopBarProgressListener): Modifier
public fun Modifier.align(alignment: Alignment.Horizontal): Modifier

/**
* Prevent the element from collapsing and make it pin to the bottom of column as it collapses.
* The height of all not collapsible elements form a total minimum (collapsed) height of column.
*/
public fun Modifier.notCollapsible(): Modifier

/**
* Move element further in the collapse direction after it has collapsed, like 'pinning' to the
* bottom of column under the next element.
*
* Has no effect if combined with [notCollapsible], and makes no sense to use in combination
* with [clipToCollapse].
*/
public fun Modifier.pinWhenCollapsed(): Modifier

/**
* Clip element draw area as it collapses so that it does not draw underneath the element above.
*
Expand All @@ -339,39 +365,44 @@ public interface CollapsingTopBarColumnScope {
public fun Modifier.clipToCollapse(): Modifier

/**
* Move element further in the collapse direction after it has collapsed, like 'pinning' to the
* bottom of column under the next element.
* Registers a progress listener to be notified every time top bar column collapse height
* changes. Only the last modifier in chain takes effect.
*
* Has no effect if combined with [notCollapsible], and makes no sense to use in combination
* with [clipToCollapse].
* @param listener the listener that gets notified of every collapse progress update.
*
* @see CollapsingTopBarProgressListener
*/
public fun Modifier.pinWhenCollapsed(): Modifier
public fun Modifier.columnProgress(listener: CollapsingTopBarProgressListener): Modifier
}

private object CollapsingTopBarColumnScopeInstance : CollapsingTopBarColumnScope {

override fun Modifier.columnProgress(listener: CollapsingTopBarProgressListener): Modifier {
return then(ProgressListenerModifier(listener))
override fun Modifier.align(alignment: Alignment.Horizontal): Modifier {
return then(AlignmentModifier(alignment))
}

override fun Modifier.notCollapsible(): Modifier {
return then(NotCollapsibleModifier())
}

override fun Modifier.pinWhenCollapsed(): Modifier {
return then(PinWhenCollapsedElement)
}

override fun Modifier.clipToCollapse(): Modifier {
return then(ClipToCollapseElement)
}

override fun Modifier.pinWhenCollapsed(): Modifier {
return then(PinWhenCollapsedElement)
override fun Modifier.columnProgress(listener: CollapsingTopBarProgressListener): Modifier {
return then(ProgressListenerModifier(listener))
}
}

private class ProgressListenerModifier(
private val listener: CollapsingTopBarProgressListener,
private class AlignmentModifier(
private val alignment: Alignment.Horizontal,
) : CollapsingTopBarColumnParentDataModifier() {
override fun modifyParentData(parentData: CollapsingTopBarColumnParentData) {
parentData.progressListener = listener
parentData.alignment = alignment
}
}

Expand All @@ -381,16 +412,24 @@ private class NotCollapsibleModifier : CollapsingTopBarColumnParentDataModifier(
}
}

private data object PinWhenCollapsedElement : CollapsingTopBarColumnParentDataModifier() {
override fun modifyParentData(parentData: CollapsingTopBarColumnParentData) {
parentData.pinWhenCollapsed = true
}
}

private data object ClipToCollapseElement : ModifierNodeElement<ClipToCollapseNode>() {

override fun create(): ClipToCollapseNode = ClipToCollapseNode()
override fun update(node: ClipToCollapseNode) = Unit
override fun InspectorInfo.inspectableProperties() = Unit
}

private data object PinWhenCollapsedElement : CollapsingTopBarColumnParentDataModifier() {
private class ProgressListenerModifier(
private val listener: CollapsingTopBarProgressListener,
) : CollapsingTopBarColumnParentDataModifier() {
override fun modifyParentData(parentData: CollapsingTopBarColumnParentData) {
parentData.pinWhenCollapsed = true
parentData.progressListener = listener
}
}

Expand Down Expand Up @@ -455,10 +494,11 @@ private abstract class CollapsingTopBarColumnParentDataModifier : ParentDataModi
}

private data class CollapsingTopBarColumnParentData(
var progressListener: CollapsingTopBarProgressListener? = null,
var alignment: Alignment.Horizontal? = null,
var isNotCollapsible: Boolean = false,
var pinWhenCollapsed: Boolean = false,
var clipToCollapseHeightListener: ((Int) -> Unit)? = null,
var progressListener: CollapsingTopBarProgressListener? = null,
)

private val Placeable.columnParentData: CollapsingTopBarColumnParentData?
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,40 @@ CollapsingTopBarColumn(

See all supported placement customization Modifiers:

<details>
<summary>Align</summary>

#### Align

```kotlin
Modifier.align(Alignment.Horizontal)
```

Aligns an element horizontally within the width of `CollapsingTopBarColumn`.

```kotlin
CollapsingTopBarScaffold(
scrollMode = CollapsingTopBarScaffoldScrollMode.collapse(expandAlways = false),
topBar = { topBarState ->
CollapsingTopBarColumn(topBarState) {
SampleTopAppBar(
modifier = Modifier.notCollapsible(),
)
AlignmentElement(
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
},
body = {
SampleContent()
},
)
```

> In this example the element is aligned to the center of the column.

</details>

<details>
<summary>Not collapsible</summary>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.flaringapp.compose.topbar.sample.shared.ui.samples.basic.CollapsingEx
import com.flaringapp.compose.topbar.sample.shared.ui.samples.basic.CollapsingExpandAtTopSample
import com.flaringapp.compose.topbar.sample.shared.ui.samples.basic.EnterAlwaysCollapsedSample
import com.flaringapp.compose.topbar.sample.shared.ui.samples.column.AlternatelyCollapsibleColumnSample
import com.flaringapp.compose.topbar.sample.shared.ui.samples.column.ColumnAlignmentSample
import com.flaringapp.compose.topbar.sample.shared.ui.samples.column.ColumnInStackSample
import com.flaringapp.compose.topbar.sample.shared.ui.samples.column.ColumnMovingElementSample
import com.flaringapp.compose.topbar.sample.shared.ui.samples.column.ColumnPinnedElementsSample
Expand All @@ -54,6 +55,7 @@ object CollapsingTopBarSampleGroups {
ReverseCollapsibleColumnSample,
AlternatelyCollapsibleColumnSample,
ColumnInStackSample,
ColumnAlignmentSample,
ColumnPinnedElementsSample,
ColumnMovingElementSample,
)
Expand Down
Loading
Loading