Status Update
Comments
or...@gmail.com <or...@gmail.com> #2
Thanks fir this feature request.
Tricky request you have here as it involved more than one request to solve this properly :)
Partially this will be possible, but via slightly different API. I need to see for sure, but ScrollableColumn
and friends will consume InteractionState
that have Interaction.Drag
inside when user is dragging, and, probably, settling
(names TBD) when it's flinging. This way, you will be able to react and do smth on the interaction that are happening inside the scroll.
By do smth I mean smth that doesn't affect overall scrolling, but it seems like you want to have exactly this (snapping behaviour). This is somewhat tricky to do right, as snapping after the fling has ended will result with the bad user experience as you want to start snapping way before the fling has ended. So, there should be a proper way to make such UI
Several alternatives here (both from our side and your side):
- Wait until we expose
InteractionState
and listen to it +scrollState.value
to trigger animations. Tricky and fragile as you will have two sources of truth. - Make your own component that does that behaviour using current version of nested scrolling. You can make your own component that uses
Modifier.scrollable
that wraps ScrollableColumn. WhenScrollableColumn
hits the bound, dragging deltas will flow to the parent above (not fling though), so you can detect it and trigger anything you like. - Wait until we provide proper nested scrolling support and do 2 properly to support this.
I will leave this open until we have InteractionState
supported, let me know if you have any questions
or...@gmail.com <or...@gmail.com> #3
Thanks for your response
Indeed waiting for the fling to end is bad for user experience.
- Would this also expose the velocity? I feel like this solution would basically be the same thing than using
rawDragGestureFilter
with a less-weird api (except the gesture filter is not getting the velocity). If I'm wrong about it being the same kind of solution with a different API please correct me.
The issue with not having the velocity
propagated is that the implementation is not great, since we don't know in which direction the scroll was occurring. So we can only snap depending on scrollState.value
. This could be solved by saving the initial position when the drag starts and compare with the value when stopping, but it feels really hacky. So having this kind of information in the state (velocity, and thus direction by its sign, and also the initial value from drag started).
- It does feel like the current implementation of the CollapsingToolbar view implementation. But I'm not sure I understood it completely
When ScrollableColumn hits the bound, dragging deltas will flow to the parent above What do you mean by "hits the bounds"?
Wouldn't the parent wrapping the ScrollableColumn
always intercept the scroll events since higher in the hierarchy?
I'm not sure how this would work, since it's not a huge priority, I don't mind waiting proper nested scrolling support. Though I'm not sure how this would solve the issue here, so would appreciate more details if you have time to spare :)
I do feel like snapping is a bit tricky to implement, maybe I should've opened a different feature request for this :)
Again thanks for the details
jo...@google.com <jo...@google.com> #4
Branch: androidx-master-dev
commit 03b8c488e9eb8764bd36e30590f5171823f9560a
Author: Matvei Malkov <malkov@google.com>
Date: Thu Oct 08 18:19:09 2020
Add InteractionState to scrollable and corresponding containers, add animation observation to LazyList
This CL adds InteractionState to the ScrollableColumn/Row and Lazy lists to allow people to observe interactions that are happening on this element. While doing so, to make functionality on par, I added isAnimationRunning and stopAnimation is LazyState, so people won't miss this menthods while migrating from ScrollableColumn.
Change-Id: I815666e1b4544dcd5da9f253ec1b539fdd777529
Relnote: Added interactionState to Modifier.scrollable, ScrollableColumn and LazyColumnFor
Fixes: 169509805
Test: added
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/public_plus_experimental_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/ListDemos.kt
M compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ScrollerSamples.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
st...@gmail.com <st...@gmail.com> #5
bo...@gmail.com <bo...@gmail.com> #6
I got the functionality working in my project but am left with the indicator visible on the screen. I thought perhaps there was some interaction with other things in my project, but I was able to reproduce it in a sample project. I used the Android Studio (macOS, see version at the bottom) new project wizard to create an empty compose activity project and then just added the code from the above PullRefreshSamples.kt. At first I thought I was not able to reproduce it but setting my background to be not white then showed me that it is reproducible. See the attached sample project.
I tried poking around to see if there was something obvious for why this is happening but didn't have any luck. For now, I'm going to revert to using the Accompanist implementation as a workaround. I hope this sample is helpful in figuring this out.
Android Studio Dolphin | 2021.3.1 Patch 1
Build #AI-213.7172.25.2113.9123335, built on September 29, 2022
Runtime version: 11.0.13+0-b1751.21-8125866 x86_64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
macOS 13.0
lp...@google.com <lp...@google.com> #7
PullRefresh
does not clip, so the indicator is drawn outside the bounds. You can change this in your project by doing:
Box(Modifier.clipToBounds().pullRefresh(state)) {
But maybe we should investigate better documentation for this / considering default behaviour
ap...@google.com <ap...@google.com> #8
Branch: androidx-main
commit b60f5bbe2a643390b95e1130e6a55048e22364cd
Author: Louis Pullen-Freilich <lpf@google.com>
Date: Thu Nov 17 18:07:08 2022
Adds clipping to Modifier.pullRefreshIndicatorTransform
This ensures that when the indicator is offset outside of its bounds (i.e it has not been dragged down yet / is not refreshing), it is not visible. This fixes cases where there is no app bar to draw over the offset indicator / the indicator background color is different from the background it is drawn over.
Bug:
Test: PullRefreshIndicatorTransformTest
Change-Id: Ic15dd35a5b3fd851bae3bc8cc6eda826e2005394
A compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransformTest.kt
M compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt
ap...@google.com <ap...@google.com> #9
Branch: androidx-main
commit 4e301885e5b470a41320fd3900764a2ba5738d53
Author: Louis Pullen-Freilich <lpf@google.com>
Date: Mon Nov 21 19:03:53 2022
Fixes PullRefreshState not resetting indicator position back to 0
If onRefresh never ended up changing refreshing, or refreshing changed to true and false within the same frame so the state write was batched, the indicator would get stuck at its last position as we never animated it back to zero. Instead we now always animate the position back to zero, and any future change to setRefreshing will interrupt this animation and set it to the new correct position.
Fixes:
Test: PullRefreshStateTest
Relnote: "Fixed an issue where PullRefreshIndicator could get stuck after onRefresh is called, if the refreshing state was not changed to true."
Change-Id: Ie24160bf51bd010d165d75675cfb2c51f28dde04
M compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshStateTest.kt
M compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshState.kt
ca...@gmail.com <ca...@gmail.com> #10
Hi, in what version can I see this fix ?
lp...@google.com <lp...@google.com> #11
It hasn't released yet, but it should be in 1.4.0-alpha03 when it released
ju...@google.com <ju...@google.com> #12
The following release(s) address this bug.It is possible this bug has only been partially addressed:
androidx.compose.material:material:1.4.0-alpha03
al...@onvista.de <al...@onvista.de> #13
The Box(Modifier.clipToBounds().pullRefresh(state)) {
did not fix the issue. A LaunchedEffect
delegated refreshing state fixes the issue:
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun PullRefreshBox(
modifier: Modifier = Modifier,
refreshing: Boolean,
onRefresh: () -> Unit = {},
content: @Composable () -> Unit,
) {
// The LaunchedEffect is needed to fix the hiding of the indicator: https://issuetracker.google.com/issues/248274004
// Without this workaround the indicator would be visible even if the loading finished
var isRefreshingWorkaround by remember { mutableStateOf(refreshing) }
LaunchedEffect(key1 = refreshing) {
isRefreshingWorkaround = refreshing
}
val state = rememberPullRefreshState(isRefreshingWorkaround, onRefresh)
Box(
modifier = modifier.pullRefresh(state),
) {
content()
PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = isRefreshingWorkaround,
state = state,
contentColor = MaterialTheme.colorScheme.primary,
)
}
}
jo...@gmail.com <jo...@gmail.com> #14
lp...@google.com <lp...@google.com> #15
I couldn't reproduce anything with the code provided, please file a new bug with a detailed reproduction, with the latest version of the material library.
ch...@gmail.com <ch...@gmail.com> #16
I can still reproduce the bug on compose version 1.4.0 without the workaround above, any update about this bug?
lp...@google.com <lp...@google.com> #17
Please file a new bug with a standalone sample project that reproduces this issue, I cannot reproduce on my end so it might be specific to your setup.
ma...@astropay.com <ma...@astropay.com> #18
hi...@gmail.com <hi...@gmail.com> #19
ma...@arsaga.jp <ma...@arsaga.jp> #20
WorkAround
if (state.progress > 0 || isRefreshing) PullRefreshIndicator(
refreshing = isRefreshing,
state = state,
modifier = Modifier.align(Alignment.TopCenter)
)
ne...@appsfactory.de <ne...@appsfactory.de> #21
lp...@google.com <lp...@google.com> #22
Could you share some sample code to reproduce the issue? Thanks
my...@gmail.com <my...@gmail.com> #23
I'm still able to reproduce this issue but in a corner case: the refresh runs too fast and isRefreshing
keeps false all the time, and refreshState.distanceFraction
will be 1f
which makes the indicator visible.
Here is a sample project:
me...@gmail.com <me...@gmail.com> #24
ca...@gmail.com <ca...@gmail.com> #25
I still have the same issue years later. If I use a pulltorefresh box and in the "onRefresh" don't have a function that takes a process time before setting isRefreshing to false (Like delay or a suspend fun that is not super fast), then the loading indicator will get stuck and never dissapear.
setting a delay of hald a second fixes it but it's not optimal. Please, it's been years and this is an easy fix. Please, fix it.
Description
Jetpack Compose version: 1.3.0-beta03 Jetpack Compose component used: PullRefresh Android Studio Build: Dolphin Kotlin version: 1.7.10
Steps to Reproduce or Code Sample to Reproduce:
delay()
inrefresh()
. The indicator continues to be visible (with just white circle) after refresh (see attached screenshot).Stack trace (if applicable):