Skip to content

Commit

Permalink
moved common logic to StickyHeadersLayout
Browse files Browse the repository at this point in the history
  • Loading branch information
gregkorossy committed Aug 14, 2024
1 parent 5b08d0f commit 6adab49
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 150 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@
![badge-js](http://img.shields.io/badge/platform-JS-F8DB5D.svg?style=flat)
![badge-wasm](http://img.shields.io/badge/platform-Wasm-624FE8.svg?style=flat)

# Lazy Sticky Headers
# Advanced Sticky Headers

Compose Multiplatform library for adding sticky headers to lazy lists.
Compose Multiplatform library for adding advanced sticky headers to lazy lists and grids.

## Preview

<p align="center">
<img src="asset/preview_contacts.gif" width="270">
<img src="asset/preview_calendar.gif" width="270">
<img src="asset/preview_grid.gif" width="270">
</p>

## Getting started

```kotlin
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha02")
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha03")
```

<details>
Expand All @@ -38,7 +39,7 @@ If you target a subset of the library supported platforms, add the library to yo
kotlin {
sourceSets {
commonMain.dependencies {
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha02")
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha03")
// ...
}
}
Expand All @@ -53,12 +54,12 @@ add the library separately to each supported target:
kotlin {
val desktopMain by getting {
dependencies {
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha02")
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha03")
// ...
}
}
androidMain.dependencies {
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha02")
implementation("me.gingerninja.lazy:sticky-headers:0.1.0-alpha03")
// ...
}
// other targets...
Expand Down
Binary file modified asset/header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added asset/preview_grid.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 27 additions & 1 deletion sticky-headers/api/android/sticky-headers.api
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ public final class me/gingerninja/lazy/GridStickyHeadersKt {
public static final fun StickyHeaders (Landroidx/compose/foundation/lazy/grid/LazyGridState;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class me/gingerninja/lazy/LazyGridInfoProvider : me/gingerninja/lazy/StickyLayoutInfoProvider {
public static final field $stable I
public fun <init> (Landroidx/compose/foundation/lazy/grid/LazyGridState;)V
public fun getBeforeContentPadding ()I
public fun getMainAxisItemSpacing ()I
public fun itemOffsetAt (ILandroidx/compose/foundation/gestures/Orientation;)Ljava/lang/Integer;
}

public final class me/gingerninja/lazy/LazyGridItem {
public static final synthetic fun box-impl (Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;)Lme/gingerninja/lazy/LazyGridItem;
public static fun constructor-impl (Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;)Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;
Expand All @@ -22,6 +30,14 @@ public final class me/gingerninja/lazy/LazyGridItem {
public final synthetic fun unbox-impl ()Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;
}

public final class me/gingerninja/lazy/LazyListInfoProvider : me/gingerninja/lazy/StickyLayoutInfoProvider {
public static final field $stable I
public fun <init> (Landroidx/compose/foundation/lazy/LazyListState;)V
public fun getBeforeContentPadding ()I
public fun getMainAxisItemSpacing ()I
public fun itemOffsetAt (ILandroidx/compose/foundation/gestures/Orientation;)Ljava/lang/Integer;
}

public final class me/gingerninja/lazy/LazyListItem {
public static final synthetic fun box-impl (Landroidx/compose/foundation/lazy/LazyListItemInfo;)Lme/gingerninja/lazy/LazyListItem;
public static fun constructor-impl (Landroidx/compose/foundation/lazy/LazyListItemInfo;)Landroidx/compose/foundation/lazy/LazyListItemInfo;
Expand All @@ -40,10 +56,14 @@ public final class me/gingerninja/lazy/LazyListItem {
public final synthetic fun unbox-impl ()Landroidx/compose/foundation/lazy/LazyListItemInfo;
}

public final class me/gingerninja/lazy/StickyHeadersKt {
public final class me/gingerninja/lazy/ListStickyHeadersKt {
public static final fun StickyHeaders (Landroidx/compose/foundation/lazy/LazyListState;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class me/gingerninja/lazy/StickyHeadersLayoutKt {
public static final fun StickyHeadersLayout (Ljava/util/List;Landroidx/compose/foundation/gestures/Orientation;ZLme/gingerninja/lazy/StickyLayoutInfoProvider;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class me/gingerninja/lazy/StickyInterval {
public static final field $stable I
public fun <init> (Ljava/lang/Object;II)V
Expand All @@ -60,3 +80,9 @@ public final class me/gingerninja/lazy/StickyInterval {
public fun toString ()Ljava/lang/String;
}

public abstract interface class me/gingerninja/lazy/StickyLayoutInfoProvider {
public abstract fun getBeforeContentPadding ()I
public abstract fun getMainAxisItemSpacing ()I
public abstract fun itemOffsetAt (ILandroidx/compose/foundation/gestures/Orientation;)Ljava/lang/Integer;
}

28 changes: 27 additions & 1 deletion sticky-headers/api/desktop/sticky-headers.api
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ public final class me/gingerninja/lazy/GridStickyHeadersKt {
public static final fun StickyHeaders (Landroidx/compose/foundation/lazy/grid/LazyGridState;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class me/gingerninja/lazy/LazyGridInfoProvider : me/gingerninja/lazy/StickyLayoutInfoProvider {
public static final field $stable I
public fun <init> (Landroidx/compose/foundation/lazy/grid/LazyGridState;)V
public fun getBeforeContentPadding ()I
public fun getMainAxisItemSpacing ()I
public fun itemOffsetAt (ILandroidx/compose/foundation/gestures/Orientation;)Ljava/lang/Integer;
}

public final class me/gingerninja/lazy/LazyGridItem {
public static final synthetic fun box-impl (Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;)Lme/gingerninja/lazy/LazyGridItem;
public static fun constructor-impl (Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;)Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;
Expand All @@ -22,6 +30,14 @@ public final class me/gingerninja/lazy/LazyGridItem {
public final synthetic fun unbox-impl ()Landroidx/compose/foundation/lazy/grid/LazyGridItemInfo;
}

public final class me/gingerninja/lazy/LazyListInfoProvider : me/gingerninja/lazy/StickyLayoutInfoProvider {
public static final field $stable I
public fun <init> (Landroidx/compose/foundation/lazy/LazyListState;)V
public fun getBeforeContentPadding ()I
public fun getMainAxisItemSpacing ()I
public fun itemOffsetAt (ILandroidx/compose/foundation/gestures/Orientation;)Ljava/lang/Integer;
}

public final class me/gingerninja/lazy/LazyListItem {
public static final synthetic fun box-impl (Landroidx/compose/foundation/lazy/LazyListItemInfo;)Lme/gingerninja/lazy/LazyListItem;
public static fun constructor-impl (Landroidx/compose/foundation/lazy/LazyListItemInfo;)Landroidx/compose/foundation/lazy/LazyListItemInfo;
Expand All @@ -40,10 +56,14 @@ public final class me/gingerninja/lazy/LazyListItem {
public final synthetic fun unbox-impl ()Landroidx/compose/foundation/lazy/LazyListItemInfo;
}

public final class me/gingerninja/lazy/StickyHeadersKt {
public final class me/gingerninja/lazy/ListStickyHeadersKt {
public static final fun StickyHeaders (Landroidx/compose/foundation/lazy/LazyListState;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class me/gingerninja/lazy/StickyHeadersLayoutKt {
public static final fun StickyHeadersLayout (Ljava/util/List;Landroidx/compose/foundation/gestures/Orientation;ZLme/gingerninja/lazy/StickyLayoutInfoProvider;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class me/gingerninja/lazy/StickyInterval {
public static final field $stable I
public fun <init> (Ljava/lang/Object;II)V
Expand All @@ -60,3 +80,9 @@ public final class me/gingerninja/lazy/StickyInterval {
public fun toString ()Ljava/lang/String;
}

public abstract interface class me/gingerninja/lazy/StickyLayoutInfoProvider {
public abstract fun getBeforeContentPadding ()I
public abstract fun getMainAxisItemSpacing ()I
public abstract fun itemOffsetAt (ILandroidx/compose/foundation/gestures/Orientation;)Ljava/lang/Integer;
}

2 changes: 1 addition & 1 deletion sticky-headers/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
alias(libs.plugins.nexusPlugin)
}

version = "0.1.0-alpha02"
version = "0.1.0-alpha03"

kotlin {
jvm(name = "desktop")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,18 @@
package me.gingerninja.lazy

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo.Companion.UnknownColumn
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo.Companion.UnknownRow
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import kotlin.jvm.JvmInline

/**
Expand Down Expand Up @@ -107,10 +100,8 @@ fun <T : Any> StickyHeaders(
key: (item: List<LazyGridItem>) -> T?,
// contentType: (item: LazyListItem) -> Any? = { null },
modifier: Modifier = Modifier,
content: @Composable (stickyKey: StickyInterval<T>) -> Unit,
content: @Composable (stickyInterval: StickyInterval<T>) -> Unit,
) {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl

val keyFactory = rememberUpdatedState(key)

val orientation by remember(state) {
Expand All @@ -125,14 +116,6 @@ fun <T : Any> StickyHeaders(
}
}

fun IntOffset.dirOffset(): Int {
return if (orientation == Orientation.Vertical) {
y
} else {
x
}
}

val keys: List<StickyInterval<T>> by remember(state) {
derivedStateOf {
state.layoutInfo.visibleItemsInfo
Expand Down Expand Up @@ -212,64 +195,36 @@ fun <T : Any> StickyHeaders(
}
}

Box(
modifier = modifier.clipToBounds(),
) {
keys.forEach { interval ->
key(interval.key) { // TODO ReusableContentHost { }, see LazyLayoutItemContentFactory
Box(
modifier = Modifier
.run {
if (orientation == Orientation.Horizontal && reverseLayout) {
align(Alignment.CenterEnd)
} else if (orientation == Orientation.Vertical && reverseLayout) {
align(Alignment.BottomCenter)
} else {
this
}
}
.graphicsLayer {
val spacing = state.layoutInfo.mainAxisItemSpacing

val next = state.layoutInfo.visibleItemsInfo
.firstOrNull { it.index == interval.endIndex }

val item = state.layoutInfo.visibleItemsInfo
.firstOrNull { it.index == interval.startIndex }

val nextOffset = next?.offset?.dirOffset() ?: Int.MAX_VALUE

val beforePadding = state.layoutInfo.beforeContentPadding

val switchDirection =
reverseLayout.xor(isRtl && orientation == Orientation.Horizontal)

val direction = if (switchDirection) -1f else 1f

if (item == null) { // don't show the item if it's not visible anymore
alpha = 0f
} else {
if (orientation == Orientation.Vertical) {
val y = (nextOffset - spacing - size.height + beforePadding)
.coerceAtMost(0f)
val offset =
(item.offset.dirOffset() + beforePadding).coerceAtLeast(0)

translationY = (offset + y) * direction
} else {
val x = (nextOffset - spacing - size.width + beforePadding)
.coerceAtMost(0f)
val offset =
(item.offset.dirOffset() + beforePadding).coerceAtLeast(0)
StickyHeadersLayout(
keys = keys,
orientation = orientation,
reverseLayout = reverseLayout,
layoutInfoProvider = remember(state) { LazyGridInfoProvider(state) },
modifier = modifier,
content = content,
)
}

translationX = (offset + x) * direction
}
}
},
) {
content(interval)
/**
* Provides basic info about the grid that will be used by [StickyHeadersLayout].
*/
class LazyGridInfoProvider(private val listState: LazyGridState) : StickyLayoutInfoProvider {
override val mainAxisItemSpacing: Int
get() = listState.layoutInfo.mainAxisItemSpacing

override val beforeContentPadding: Int
get() = listState.layoutInfo.beforeContentPadding

override fun itemOffsetAt(index: Int, orientation: Orientation): Int? {
return listState.layoutInfo.visibleItemsInfo
.firstOrNull { it.index == index }
?.offset
?.run {
if (orientation == Orientation.Vertical) {
y
} else {
x
}
}
}
}
}
Loading

0 comments on commit 6adab49

Please sign in to comment.