For a long time, I'v struggled with notion of keep...
# compose
t
For a long time, I'v struggled with notion of keeping list content visible in a list when the environs of the list change. LazyListState has a nice API for scrolling as long as you're interested in the top of the list. But it would be nice (imo) if it could do things like the UIKit TableView: https://developer.apple.com/documentation/uikit/uitableview/1614997-scrolltorow . Said API allows you to specify where you scroll the target row/item to (top, middle, bottom, or indifferent). Being in particular need of the last case (indifference), I attempted my own extension to LazyListInfo.makeVisible (code in thread). It works, but is probably overly naive? Would love any feedback/direction from all you real Kotlin Gurus
Copy code
val LazyListItemInfo.endOffset get() = offset + size

suspend fun LazyListState.animateMakeVisible(target: Int) {
    // filter out out-of-range targets
    if (target !in 0 until layoutInfo.totalItemsCount) {
       return
    }
    // easy branch first, is our target "above" visible range, if so scroll to it
    val firstFullIndex = when (firstVisibleItemScrollOffset) {
       0 -> firstVisibleItemIndex
       else -> firstVisibleItemIndex + 1
    }
    if (target < firstFullIndex) {
       return animateScrollToItem(target)
    }
    // heftier branch, as long as the last item isn't visble, keep scrolling by the last items size until we arrive out our target
    while ((layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: target) < target) {
       layoutInfo.visibleItemsInfo.lastOrNull()?.let { lastItem ->
          scrollBy(lastItem.size.toFloat()) // don't animate this one, that creates a comical effect :D
       }
    }
    // and finally do any sub-item adjustment we need
    layoutInfo.visibleItemsInfo.lastOrNull()?.let { lastItem ->
       if (lastItem.index == target) {
          animateScrollBy((lastItem.endOffset - layoutInfo.viewportEndOffset).toFloat())
       }
    }
}
z
We’ve been solving this from the container’s perspective – so a container can specify where it wants to target bringIntoView requests from. Would that work for you?
t
I can play with that some. I had in the past, and didn't have luck. There's a parallel piece to this. That's how to make sure sure list content is scrolled to be visible when the IME/keyboard opens. I have not had good luck with the imePadding stuff for lists. It'll move the list, but doesn't seem to have the werewithall to keep content specified by the user (at least not this simple programmer) in view. I found though that I could use an effect like this, in conjunction with the above:
Copy code
val after = WindowInsets.imeAnimationTarget.getBottom(LocalDensity.current)
val before = WindowInsets.imeAnimationSource.getBottom(LocalDensity.current)
val listLayout by remember { derivedStateOf { scrollState.layoutInfo } }
var visibleIndex by remember { mutableStateOf<Int?>(null) }
LaunchedEffect(after, before, listLayout.viewportEndOffset, visibleIndex) {
    if (after == before) {
       visibleIndex?.let { index ->
          scrollState.animateMakeVisible(index)
       }
    }
}
Maybe I need to shift back to bringIntoView requestors; I had tried to link them to the focusChanged, but perhaps it's an ordering issue. The above seems to be pretty reliable at triggering "the keyboard is done now, fix things so they're visible". I'll retry and report back.
z
We are also planning to add an api to let you specify padding around the BIV target
t
Naive reading of this... Looking at the last example in https://developer.android.com/reference/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester , if rather than single circle, I had 100 circles and some sort of selection state for selecting 1-100 (say a menu or a list or an input field), I'd need to have a list of 100 associated BIVRs so that I could ping the correct one. Is that correct?
It may be I'm doing this wrong. But I'm not able to make this work. I made a list of requestors, associated one with each list item, and then tell the target one to bringIntoView() when the keyboard ends changing. but it does nothing. I am running 1.6.x? I can dump an example here if you want
z
Yes, or you could have one for all of them then request a smaller area inside each one