Is there a way to get the value of the y position while the screen is scrolling?
I have a situation like this:
LazyColumn() {
item {
box()
box()
box()
ScrollableTabRow(){}
}
itemsIndexed {...}
I need to know when the ScrollableTabRow moves past a certain position on y axis (for example lets say 100.dp )
I tried working with .scrollable()
(something like scrollable)
and with scroll gesture filter
(something like gesture
and with dragGestureFilter
but i cannot get it to work, am i doing something wrong, or is it just not possible?
Basically what i need to do is for the scrollable tab row to act as sticky header after it touches top of the screen.
What i wanted to do is, after scrollable row touches the top of the screen, create some sort of mockup for it at the top of the screen, but maybe there is some easier way?
App is written strictly in compose, so i dont have access to any of the libraries that provide such implementation
Use stickyHeader.
LazyColumn {
item { ... }
stickyHeader {
ScrollableTabRow(...) { ... }
}
item { ... }
}
Related
Current State:
I have a Viewpager2 which contains a bottom sheet (using BottomSheetBehaviour). When the bottom sheet collapses, the Viewpager2 doesn't resize to fit the now shorter content. I also have a map (using MapBox's MapView) that shows underneath the ViewPager2.
Since this description is a little ambiguous, I have also created a minimal reproducible example (hosted on GitHub) for clarification. In order to get MapBox to show a map, you will have to enter your secret/public tokens in gradle.properties and strings.xml files respectively. Even if you don't, you can probably get what I'm saying by looking at the scale bar at the top left corner of the MapView.
What I want to happen:
When the bottom sheet collapses, the section of the view that the bottom sheet had previously occupied that is now transparent does not handle user interaction but instead passes the events on to the FragmentContainerView that is shown beneath it.
Problem:
When the bottom sheet collapses, the height of the ViewPager2 stays the same, so touch events within the aforementioned area are handled as per normal by the ViewPager2, when they should intuitively be handled by the MapView below, since it is now visible in that area. You can just drag your finger or pinch the area to get what I mean.
What I've tried so far:
Making ViewPager2 dynamically 'wrap content'
It seems to me like ViewPager2 will not handle this out of the box from this Issue.
It's probably possible to get the expanded and collapsed height of each fragment in the ViewPager2 and then reset ViewPager2's height each time the fragment/page changes and each time the bottom sheet goes to the expanded/collapsed state but that that requires a lot of calls to requestLayout() which makes the UI flicker and disrupts animations.
Handling touch events
I tried the following, but it seems that perhaps ViewPager2 requires the touch events to be registered, because trying to swipe to the next page crashed the app. However, I'm not very familiar with handling touch events or ViewPager2's implementation and I didn't go too deep into exploring this, so input on the feasibility of this/whether it even makes sense will be appreciated.
viewPager2[position].setOnTouchListener { v, event ->
// If background is collapsed, allow touch events to pass through transparent space in ViewPager2
if (event.y < bottomSheetBehavior.peekHeight && isBottomSheetCollapsed) {
false
// Else, handle them as per normal
} else {
when (event.action) {
ACTION_DOWN -> v.performClick()
}
true
}
}
Note:
I also want the bottom sheet to be able to swipe to the next page like a viewpager, which is why I put the bottom sheet inside the viewpager. Something like how Citymapper does it.
I ended up doing the following:
Collapse bottomSheet and change the height of viewPager2 to match the collapsed height by default
Set an onTouchListener() on bottomSheet to detect upward drag events, which when detected would increase the height of viewPager2 and expand bottomSheet
Setting the onTouchListener():
binding.bottomSheet.setOnTouchListener { _, event ->
when (event.action) {
ACTION_DOWN -> {
touchStartY = event.rawY
true
}
ACTION_UP -> {
false
}
ACTION_MOVE -> {
touchCurrentY = event.rawY
if (touchStartY - touchCurrentY > TOUCH_THRESHOLD && isBottomSheetCollapsed) {
// Update flag
isBottomSheetCollapsed = false
// Change viewPager2 height
viewPager2.updateLayoutParams {
height = bottomSheetExpandedHeight
}
// I don't think that updateLayoutParams calls requestLayout(), but you
// would want to expand the bottomSheet only after the viewPager2 height has updated
viewPager2.doOnLayout {
bottomSheetBehavior.state = STATE_EXPANDED
}
false
} else true
}
else -> true
}
}
Note: Might need to play around with doOnLayout() or doOnNextLayout() to get the expanded height of the bottom sheet if it is set to WRAP_PARENT in xml.
I'm trying to have a little icon at the top of a LazyColumn that scrolls like any other item in the list. My googling has yielded no luck and I'm wondering if it's even possible yet. Looking for a resource that could help me implement this. To be clear I know stickyHeader exists and that's not what I'm looking for since it will stay at the top of the screen and not scroll with the items.
You can just add another item in the body of the LazyColumn if it's just like any other row and you want it to scroll.
LazyColumn {
item {
Icon()
}
items(myItemList) {
}
}
I want to achieve this using jetpack compose.
A is scrollable list of row items. When A is smaller than screen(or parent) size, B(footer) should be placed bellow the last row. When A + B are bigger than screen size, then B becomes fixed at the bottom and A content is scrollable. I'm wondering if there is easy way to achieve this, using compose ConstraintLayout.
I found solution for it. I had to use Modifier.weight(1f, false) at A.
There are many ways to do this but the most efficient way is to use the bottomBar property of Scaffold View.
For example:
#Composable
fun RenderContent() {
Scaffold(
topBar = { /* Your app bar goes here */ },
bottomBar = { /* Anything you place in here will be stick to the bottom. */ }
) {
// ... Rest of the content
// Benefits: If you make the content scrollable and
// the `bottomBar` composable remain on top of the content
}
}
If I understood you correctly you are trying to add a footer view to a LazyColmn. The LazyColumn in JetpackCompose is behaving like the native ListView. I will give an example of a LazyColumn that has a view(composable) at the bottom that is shown when you scroll at the end of the list:
LazyColumn() {
item {
//If you want the first item to stay always on top as you scroll use stickyHeader for this section.
Text("This is the first item")
}
items(listItems.size) {
Text("This is a normal list item")
}
item {
Text("This item will behave as a footer and display at the end of the list")
}
}
Hope this helps somebody.
Thanks
I'm trying to implement a screen that use a vertical scroll to show all the contents and some sections of this screen has a horizontal scroll with cards like this:
The screen composable:
LazyColumn{
item { header }
item { texts }
item {
LazyRow{
items(firstCardList){}
}
LazyRow{
items(secondCardList){}
}
}
}
The scrolls are working fine but not smoothly, I don't know how to explain but when I move the list with my finger, the scroll stop when I remove my finger of the screen, it's not smoothly and continuous the scroll like the vertical, looks like the scroll animation is stuck.
I know that in xml I can use nestedScrollingEnabled = false and this problem is solved, but I don't know how to do this in compose.
I have to implement a transition of either a viewpager o a recyclerview, not sure yet which could fit better. The idea is to expand de ViewPager or Recycler and show more information when this is expanded and less information when is collapsed.
Expand and collapse with MotionLayout is quite easy however I cannot find the approach for animate and change the content shown in each element of the the adapter.
is it posible to animate the content of an adapter simultaneously?
I an ugly solution for you. I use a recycler-view that contains some motion layouts. There is an expanded state and a collapsed state, we only want one expanded at a time.
A method to close positions in the fragment/activity
fun collapseMotionLayout(positions: IntArray){
positions.forEach { position ->
recyclerView.findViewHolderForAdapterPosition(position)?.let { childView ->
//transitionToEnd() or transitionToStart() or toggleUI() or whatever
(childView.itemView.findViewWithTag("motionlayout") as? MotionLayout)?.collapse()
}
}
And then a fragment/activity call back in the motion layout's TransitionListener that you'll define in the viewholder (we use a custom view)
tListener = object: TransitionListener{
override onCompleted(){
callBack.invoke(positionsForAnimation())
}
}
I only see this collapse a single motion layout at a time. I have found that multiple motion layouts running simultaneously will will cause performance issues.