Status Update
Comments
ma...@google.com <ma...@google.com> #2
I believe this should be already possible with APIs we expose in 1.4+. The only part needed for usecase is to open the way to remove overscroll from the Modifier.scrollable and/or LazyLists?
cj...@gmail.com <cj...@gmail.com> #3
Yes exactly. If we have control over the overscroll effect (either to replace or augment the default behavior) then this is already possible.
lp...@google.com <lp...@google.com> #4
This should be possible with scrollable
which exposes an OverscrollEffect
parameter, but not for lazy APIs currently. Probably we will add support for configuring that on lazy APIs as well in the future
cj...@gmail.com <cj...@gmail.com> #5
Yes this is possible with scrollable
although in my experience most people use the (subjectively) more convenient verticalScroll
(with the nice default behaviour) and its not exposed on that API.
In any case, we need this on lazy APIs as that what we use for this particular screen (Its a very long list of components and a Column
will not suffice)
lp...@google.com <lp...@google.com> #6
It should be possible to implement the overscroll part of what you want to do here anyway with the current overscroll APIs (they were simplified recently in androidx.compose.foundation.foundation-1.4.0-alpha05) - curious to hear if you have tried to implement this at least for the scrollable case, to see if there is anything else missing from the API here for your use case
cj...@gmail.com <cj...@gmail.com> #7
So I tried this & I was able to very easily implement what we needed with the current apis. I think they are intuitive enough.
The one thing that stood out to me was the fact that scrollable
applies the effectModifier directly to the scroll container unconditionally, not a problem for the common case, but for our use case, the header image (that should be scaled) is nested within the scrollable container.
cj...@gmail.com <cj...@gmail.com> #8
Above just meant that I had to have my effectModifier
as a no-op and instead expose the graphicsLayer { scale = ... }
modifier directly for the header image to use
lp...@google.com <lp...@google.com> #9
The one thing that stood out to me was the fact that scrollable applies the effectModifier directly to the scroll container unconditionally, not a problem for the common case, but for our use case, the header image (that should be scaled) is nested within the scrollable container.
Yes, we plan to separate the controller / effect part of the API, to make it easier to apply the effect separately. Thanks for confirming that this is needed for your use case as well! If you have any sample code you can share of how you implemented this it would be helpful to see and make sure we can support it properly with any new API changes
cj...@gmail.com <cj...@gmail.com> #10
Yeah sure, the gist of the implementation is basically:
@Composable
private fun rememberScalePhotoOverscrollEffect(): ScalePhotoOverscrollEffect {
val density = LocalDensity.current
return remember(density) { ScalePhotoOverscrollEffect(with(density) { 148.dp.toPx() }) }
}
// Todo: Its not possible to actually scale the header photo yet, see b/266550551
private class ScalePhotoOverscrollEffect(private val maxPullDistance: Float) : OverscrollEffect {
var pullDistance by mutableStateOf(0f)
val scale by derivedStateOf { 1f + (0.1f * pullDistance / maxPullDistance) }
val modifier = Modifier.graphicsLayer { scale(scale) }
override val isInProgress get() = pullDistance > 0f
// This modifier is unconditionally applied to the scrolling component.
// Make it a no-op & apply [modifier] to the image instead
override val effectModifier = Modifier
override fun applyToScroll(delta: Offset, source: NestedScrollSource, performScroll: (Offset) -> Offset): Offset {
if (source != NestedScrollSource.Drag) return performScroll(delta)
// Todo: Handle reverse main axis drag here
val available = delta - performScroll(delta)
pullDistance = (pullDistance + available.y).coerceIn(0f, maxPullDistance)
return delta // consume it all, the system doesn't need it lol
}
override suspend fun applyToFling(velocity: Velocity, performFling: suspend (Velocity) -> Velocity) {
coroutineScope {
launch { // Fling is our cue to start decay animation, animate back to zero
if (pullDistance != 0f) {
animate(
initialValue = pullDistance,
targetValue = 0f,
animationSpec = spring(),
block = { value, _ -> pullDistance = value },
)
}
}
launch { // While also performing the default fling
performFling(velocity)
}
}
}
}
cj...@gmail.com <cj...@gmail.com> #11
Is it still the plan to overhaul the overscroll implementation?
lp...@google.com <lp...@google.com> #12
cj...@gmail.com <cj...@gmail.com> #13
Amazing. Sounds good. I'll remain on the lookout.
ap...@google.com <ap...@google.com> #14
Project: platform/frameworks/support
Branch: androidx-main
Author: Louis Pullen-Freilich <
Link:
Adds overloads for scroll and lazy APIs that allow specifying overscroll
Expand for full commit details
Adds overloads for scroll and lazy APIs that allow specifying overscroll
Bug: b/266550551
Fixes: b/234274772
Fixes: b/224572538
Fixes: b/353805117
Test: new tests
Relnote: "Adds overloads to horizontalScroll, verticalScroll, LazyColumn, LazyRow, LazyHorizontalGrid, LazyVerticalGrid, LazyHorizontalStaggeredGrid, LazyVerticalStaggeredGrid, HorizontalPager, and VerticalPager with support for specifying a custom OverscrollEffect. The provided OverscrollEffect will receive events, and be rendered within the bounds of these components. Note that drawing the same OverscrollEffect twice is unsupported - so you cannot draw the same OverscrollEffect provided to one of these components separately with Modifier.overscroll. The use case of drawing the overscroll outside of the bounds of these components will be addressed separately in the future."
Change-Id: I2dc42851c824a63e495312246eb5389c33121af8
Files:
- M
compose/foundation/foundation/api/current.txt
- M
compose/foundation/foundation/api/restricted_current.txt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/BaseLazyGridTestWithOrientation.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridTest.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListHeadersTest.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListTest.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListsIndexedTest.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
- M
compose/foundation/foundation/integration-tests/lazy-tests/src/androidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
- M
compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/ScrollTest.kt
- M
compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/lazy/list/BaseLazyListTestWithOrientation.kt
- M
compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/BasePagerTest.kt
- M
compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
- M
compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/pager/PagerTest.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGrid.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridDsl.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGrid.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/Pager.kt
Hash: 18a2c333f118b50a0febd5206c2f2f31d81bb9bc
Date: Wed Oct 09 18:09:54 2024
lp...@google.com <lp...@google.com> #15
Update: we are currently working on stabilising overscroll, and we just landed a change to expose OverscrollEffect in vertical/horizontalScroll and lazy APIs. However after some discussions we decided not to go ahead with the API separation mentioned previously, this causes a few ergonomic problems and adds a lot of complexity in general in order to improve a minority of cases.
Instead though we can probably just provide a wrapper extension API like OverscrollEffect#withoutDrawing() (naming TBD, note that also this could be written in user code currently) that creates a new OverscrollEffect instance that just calls through to the initial one, but without applying the modifier. This way usage would look something like:
val myOverscrollEffect = remember { MyOverscrollEffect() }
// LazyColumn will dispatch events to overscroll, but will not render anything as this will return an empty modifier
LazyColumn(overscrollEffect = myOverscrollEffect.withoutDrawing()) {
// some content
...
// Render the custom overscroll effect on the box, which is being driven by LazyColumn
Box(Modifier.overscroll(myOverscrollEffect))...
}
pa...@gmail.com <pa...@gmail.com> #16
not sure im getting this, how would this work in a normal lazy column with items? For example i wanna apply the same overscroll effect as in IOS, would the items in the lazy column have bounce effect? or only the box would bounce? Thanks for the feature btw
lp...@google.com <lp...@google.com> #17
not sure im getting this, how would this work in a normal lazy column with items? For example i wanna apply the same overscroll effect as in IOS, would the items in the lazy column have bounce effect? or only the box would bounce? Thanks for the feature btw
If you just want the lazy column to apply a different overscroll effect, you can just provide the overscrollEffect
parameter to the lazy column. What I mentioned above is for custom cases where you want the overscroll to apply outside the bounds of the lazy column, or to a specific item inside - but not to the entire list and all of its items.
pa...@gmail.com <pa...@gmail.com> #18
Got it! Very niche usecase but its worth having at least the possibility
ap...@google.com <ap...@google.com> #19
Project: platform/frameworks/support
Branch: androidx-main
Author: Louis Pullen-Freilich <
Link:
Adds OverscrollEffect#withoutDrawing and OverscrollEffect#withoutEventHandling
Expand for full commit details
Adds OverscrollEffect#withoutDrawing and OverscrollEffect#withoutEventHandling
These APIs allow overscroll to have events dispatched to it by one component, and rendered in a separate component.
Fixes: b/266550551
Fixes: b/204650733
Fixes: b/255554340
Fixes: b/229537244
Test: OverscrollTest
Relnote: "Adds OverscrollEffect#withoutDrawing and OverscrollEffect#withoutEventHandling APIs - these APIs create a wrapped instance of the provided overscroll effect that doesn't draw / handle events respectively, which allows for rendering overscroll in a separate component from the component that is dispatching events. For example, disabling drawing the overscroll inside a lazy list, and then drawing the overscroll separately on top / elsewhere."
Change-Id: Idbb3d91546b49c1987a041f959bce4b2b09a9f61
Files:
- M
compose/foundation/foundation/api/current.txt
- M
compose/foundation/foundation/api/restricted_current.txt
- M
compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/OverscrollDemo.kt
- M
compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/OverscrollSample.kt
- M
compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/OverscrollTest.kt
- M
compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Overscroll.kt
Hash: f64e25b7a473c757d080521e7dd97b3f6670f60d
Date: Fri Nov 01 18:43:56 2024
na...@google.com <na...@google.com> #20
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.compose.foundation:foundation:1.8.0-alpha06
androidx.compose.foundation:foundation-android:1.8.0-alpha06
androidx.compose.foundation:foundation-jvmstubs:1.8.0-alpha06
androidx.compose.foundation:foundation-linuxx64stubs:1.8.0-alpha06
cj...@gmail.com <cj...@gmail.com> #21
Thank you!
Description
Jetpack Compose version: 1.4.0-alpha04
We have a screen with a "header" item containing an image at the top of a LazyColumn.
On overscroll we want to slightly scale the image but the entire overscroll API is hidden / not extensible.
Wold it be possible to expose
OverScrollEffect
either as a composition local or provide an ability to override the overscroll per scrollable (for both the lazy layouts & normalModifier.scrollable
cases)