-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Animation issue when swapping the first item in a lazy layout #4
Comments
Unfortunately that is one of the drawbacks of using I'll leave this issue open to see if anyone can find a solution. DetailsThis video shows what happens when the first and second items are swapped in a LazyColumn where every item that has Screen_recording_20231212_164220.mp4As you can see, the position of Turns out But when we are dragging So I decided to do what this Android demo does in here. When an item is dragged to replace the first visible item:
And similarly, when the first visible item is dragged to replace another item:
All this means that whenever the position of the first visible item changes, items will move around in a weird way. |
Firstly, thanks for the library - it's very useful. I have a workaround for the visual glitching when moving over the first item. In my app I'm adding a dummy element as the first item in the reorderable list. When I render the list I ensure that the dummy For example,
and
|
@spiral123 's workaround was right, i made a shorter solution for the LazyList:
Make a dummy item with enable = false then minus from and to index by 1 and you're good to go |
Can't you make the first element (partially not visible), non-draggable?, |
Is this the issue? It just got marked as fixed, so maybe the patch will be live soon |
Thank you for sharing this. I think this is the issue. I will update the library when this comes out. (the issue you mentioned took more than 2 years to get a fix ☠️) |
And the rest is the same. |
Looks like this will be released in Compose Foundation v1.7.0 which is currently in alpha. https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0-alpha07 I'll update this library once v1.7.0 make it to compose multiplatform. |
Is it this problem or not? Screen.Recording.2024-05-10.at.01.23.09.mov |
That doesn't look right. Can you share a minimal reproducible example? |
Screen.Recording.2024-05-10.at.01.55.33.movIt can be partially reproduced with my previous example if you decrease the size of the window. |
Screen.Recording.2024-05-10.at.02.01.41.movOr you can just make the tabs wider: import androidx.compose.animation.*
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import kotlinx.coroutines.launch
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@Composable
@Preview
fun App() {
MaterialTheme {
TabRow()
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App()
}
}
typealias Id = Long
data class Tab(val id: Id)
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun TabRow() {
var tabModels: List<Tab> by remember { mutableStateOf(listOf(Tab(-1))) }
val coroutineScope = rememberCoroutineScope()
val tabRowScrollState = rememberLazyListState()
val reorderableLazyColumnState =
rememberReorderableLazyListState(tabRowScrollState) { from, to ->
coroutineScope.launch {
tabModels = tabModels.toMutableList().apply {
add(to.index, removeAt(from.index))
}
}
}
var chosenId by remember { mutableStateOf(tabModels.first().id) }
BoxWithConstraints {
AnimatedVisibility(
visible = maxHeight > 300.dp,
enter = expandVertically(),
exit = shrinkVertically(),
) {
LazyRow(
state = tabRowScrollState,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 1.dp),
) {
items(tabModels, key = { model -> model.id }) { tabModel ->
ReorderableItem(
reorderableLazyColumnState,
key = tabModel.id
) { _ ->
val isSelected = tabModel.id == chosenId
val chooseThis = { coroutineScope.launch { chosenId = tabModel.id } }
Row(
Modifier
.height(IntrinsicSize.Min).draggableHandle()
) {
Tab(
selected = isSelected,
enabled = !isSelected,
modifier = Modifier
.padding(horizontal = 2.dp)
.clip(RoundedCornerShape(topEnd = 4.dp, topStart = 4.dp))
.width(200.dp),
onClick = { chooseThis() },
) {
Box(Modifier.width(IntrinsicSize.Min)) {
val textColor = if (isSelected) Color.Black else Color.Blue
val maxTabWidth = 300.dp
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.widthIn(max = maxTabWidth)
.padding(vertical = 4.dp)
.padding(start = 12.dp),
) {
Spacer(Modifier.width(6.dp))
val textStyle =
TextStyle(color = textColor, fontSize = 18.sp)
Box(Modifier.weight(1f)) {
Text(
text = tabModel.id.toString(),
style = textStyle,
maxLines = 1,
softWrap = false,
modifier = Modifier
.horizontalScroll(rememberScrollState())
)
}
val expectedCloseSize by animateDpAsState(if (tabModels.size > 1) 48.dp else 8.dp)
Box(Modifier.width(expectedCloseSize)) {
androidx.compose.animation.AnimatedVisibility(
tabModels.size > 1,
enter = scaleIn() + fadeIn(),
exit = scaleOut() + fadeOut(),
) {
IconButton(
onClick = {
},
enabled = tabModels.size > 1,
) {
Icon(
imageVector = Icons.Default.Close,
tint = textColor,
contentDescription = "Close tab"
)
}
}
}
}
}
}
}
}
}
item {
IconButton(
onClick = {
coroutineScope.launch {
tabModels += Tab(tabModels.size.toLong())
}
}
) {
Icon(
Icons.Default.Add,
contentDescription = "Add tab",
)
}
}
}
}
}
}
|
It also scrolls in this case for some reason. Screen.Recording.2024-05-10.at.02.12.13.mov |
@zhelenskiy try |
Still reproducible there |
Just the flashing or still can't move the second item to the first position? |
Screen.Recording.2024-05-10.at.02.47.14.mov |
By the way, the version is not available on GitHub. |
Hmm I will investigate further tomorrow. It's not on GitHub because I made it just for you ❤️. |
@zhelenskiy give |
I can. But it is still jumping and scrolling right. Screen.Recording.2024-05-11.at.02.50.05.mov |
Yeah I think that's the expected behavior right now. Will be fixed when Foundation v1.7.0 |
Both scrolling and jumping? Is there any workaround? |
When you have a little of space, it is still almost impossible to move the tab. It starts scrolling too fast, may drop dragging. Screen.Recording.2024-05-11.at.04.21.33.mp4 |
Yeah they jumping will happen whenever the first item moves |
Oh hmm I will need to think about this one. I think I can make the drag speed depend on the item size. |
I'll get back to you by the end of the upcoming Friday. |
@zhelenskiy should be fixed in |
It is much better! But it still drops drag when I try to move the first item. Screen.Recording.2024-05-13.at.14.09.40.mov |
Yeah so the problem is it's either this or scrolling too fast as in #4 (comment). It's caused by the fact that moving the first visible item causes strange things to happen. This too will be solved in Foundation 1.7.0. |
Ok, thanks! Let's wait then. |
Moving the first item does not work on Android. Screen.Recording.2024-05-14.at.23.25.26.mov |
|
Yes, it seems to be what happened. |
I logged move indexes during this strange movement: |
Is there a coroutine |
What do you expect me to write then? rememberReorderableLazyListState(tabRowScrollState) { from, to ->
println(from.index to to.index)
coroutineScope.launch {
viewModel.move(from.index, to.index)
}
} |
Try rememberReorderableLazyListState(tabRowScrollState) { from, to ->
println(from.index to to.index)
viewModel.move(from.index, to.index)
} |
Oh, it is already suspend. |
Now that works fine! Thanks! |
Good news! v1.7.0 is being released in June according to https://android-developers.googleblog.com/2024/05/whats-new-in-jetpack-compose-at-io-24.html, we should see v1.7.0 land in Compose Multiplatform some time in June/July. |
Is that going to fix this issue? If so, that's great since I plan to launch my app in August, and it uses your library. Appreciate all the hard work! |
Yes this issue will be fixed when Compose Foundation v1.7.0 is out. |
Possible to get beta release from your side? with compose "v1.7.0-beta02", TIA! |
I would love to but it's not possible. We'll have to wait for foundation to land in compose multiplatform. See #32 (comment) |
I just released 2.4.0-alpha01 yesterday. It uses v1.7.0 so it should fix this issue. I'm waiting for Compose Multiplatform v1.7.0 to be stable to release a stable version. |
v2.4.0 is out |
In the simple lazy column sample, swapping the first item instantly moves most of it outside the screen for a split second.
Tested in the android emulator on Android 14
Screen_recording_20231212_215838.webm
The text was updated successfully, but these errors were encountered: