onebone / compose-collapsing-toolbar Goto Github PK
View Code? Open in Web Editor NEWA simple implementation of collapsing toolbar for Jetpack Compose
License: MIT License
A simple implementation of collapsing toolbar for Jetpack Compose
License: MIT License
Description
The program force closed and get AbstractMethodError when trying to scroll the screen.
To Reproduce
Use this code, i paste it here: pastebin.
Hi, I have a use case where my title can have a different length. What I am trying to achieve is to have the collapsing toolbar size dynamically calculated during composition.
I have introduced the var titleHeight = remember { mutableStateOf(0.0) }
variable which is calculated in modifier like this
.onGloballyPositioned { textLayoutResultState.value = it.size.height }
private fun CollapsingToolbarScope.ToolbarTitle(
toolbarState: CollapsingToolbarScaffoldState,
state: InstalledBaseDetailsState,
installedBase: InstalledBase,
textLayoutResultState: MutableState<Double>,
) {
val textSize =
(MIN_TITLE_TEXT_SIZE + (MAX_TITLE_TEXT_SIZE - MIN_TITLE_TEXT_SIZE) * toolbarState.toolbarState.progress).sp
val startPadding =
(MIN_TITLE_PADDING + (MAX_TITLE_PADDING - MIN_TITLE_PADDING) * (1 - toolbarState.toolbarState.progress)).dp
Column(
modifier = Modifier.Companion
.background(color = colorResource(id = R.color.dark_orange))
.road(Alignment.CenterStart, Alignment.BottomStart)
.padding(
start = startPadding,
top = dimensionResource(id = R.dimen.grid_1_25x),
end = dimensionResource(id = R.dimen.grid_2x),
bottom = dimensionResource(id = R.dimen.grid_1x)
)
.onGloballyPositioned {
if ((it.size.height) > textLayoutResultState.value) {
textLayoutResultState.value = it.size.height
}
}
) {
Box(
modifier = Modifier
.graphicsLayer {
alpha = toolbarState.toolbarState.progress
scaleX = toolbarState.toolbarState.progress
scaleY = toolbarState.toolbarState.progress
}
.height(
height = (SYSTEM_STATUS_HEIGHT * toolbarState.toolbarState.progress).dp,
)
) {
SystemStatusSection(state.servicesStatus, installedBase)
}
Text(
modifier = Modifier.fillMaxHeight(),
//text = installedBase.name,
text = "Very very very very very very long longy very very very long long ",
style = headerTextStyle(toolbarState.toolbarState.progress),
maxLines = headerMaxLines(toolbarState.toolbarState.progress),
color = colorResource(id = R.color.black),
fontSize = textSize,
overflow = TextOverflow.Ellipsis,
)
}
}
private fun CollapsingToolbarScope.CollapsingToolbar(
toolbarState: CollapsingToolbarScaffoldState,
state: InstalledBaseDetailsState,
installedBase: InstalledBase,
onBackTap: () -> Unit
) {
val titleHeight = remember { mutableStateOf(0.0) }
Box(
modifier = Modifier
.pin()
.height(titleHeight.value.dp)
)
ToolbarTitle(toolbarState, state, installedBase, titleHeight)
Box(
modifier = Modifier
.clip(CircleShape)
.clickable(
indication = rememberRipple(bounded = true),
interactionSource = remember { MutableInteractionSource() },
onClick = {
onBackTap()
})
.padding(dimensionResource(id = R.dimen.grid_2x))
) {
Icon(
painter = painterResource(id = R.drawable.ic_arrow_back),
contentDescription = null,
tint = colorResource(id = R.color.healthy_orange)
)
}
}
Current behaviour:
Intended behaviour:
The brown view is the one I measure during the composition. When I hardcode measured value then it works perfectly but when it is calculated then the progress value is wrongly calculated. The list can be manually scrolled and after that toolbar it rendered corectly.
Any ideas how to resolve that issue?
Not sure if it's an issue related to #18 but having SwipeRefresh
as a child of the body blocks the toolbar from expanding. You can only expand the toolbar on a fling or when the SwipeRefresh
is refreshing. View system components first expand the toolbar then activates Swiperefreshlayout
pull gesture.
Lists wrapped in collapsing toolbar don't fling when scrolling down or up when toolbar is visible
CollapsingToolbarScaffoldState.toolbarState.progress
value changes when you go back in navigation stack even though no scrolling is happening. If toolbar title size depends on progress value, it visibly changes from smallest to largest when navigating back to screen.
It seems that when navigating back, two progress values are dispatched, 0.0f and then 1.0f.
Just like Scaffold, CollapsingToolbarScaffold should also have the option to add BottomBar.
How can that be achieved?
When a CollapsingToolbarScaffold is wrapping a horizontally scrolling component, then horizontal flings on it do not work. For example, consider a vertical feed with horizontal shelves :
CollapsingToolbarScaffold {
LazyColumn {
LazyRow
LazyRow
LazyRow
}
}
In this set up, vertical flings on the LazyColumn works just fine, but horizontal flings on any of the LazyRows do not work. I believe this is caused by the ScrollStrategy
implementations always consuming the full horizontal velocity of flings, when they should consume none of the horizontal velocity.
I have created a sample to reproduce the behavior on a video.
val scaffoldState = rememberCollapsingToolbarScaffoldState()
val title = "Sample title with very long text, which helps reproduce the problem"
val originalFontSize = MaterialTheme.typography.h6.fontSize
val maxFontSize = MaterialTheme.typography.h4.fontSize
val titleMaxLines: Int = Int.MAX_VALUE
CollapsingToolbarScaffold(
modifier = Modifier.fillMaxSize(),
state = scaffoldState,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbarModifier = Modifier.background(Color.White),
toolbar = {
val percent = scaffoldState.toolbarState.progress
val textSize = max(originalFontSize.value, (maxFontSize.value * percent)).sp
val leftPadding = max((1F - percent) * 72F, 16F).dp
val titleCurrentMaxLines = if (percent > 0.1) titleMaxLines else 1
Text( // this is fake item to allocate space
text = title,
modifier = Modifier
.alpha(0F)
.padding(start = 16.dp, top = 12.dp + 56.dp, end = 16.dp, bottom = 16.dp)
.pin(),
style = MaterialTheme.typography.h4,
)
Text(
text = title,
modifier = Modifier
.padding(leftPadding, 16.dp, 16.dp, 16.dp)
.road(Alignment.CenterStart, Alignment.BottomStart),
fontSize = textSize,
style = MaterialTheme.typography.h6,
maxLines = titleCurrentMaxLines,
)
}
) {
LazyColumn() {
items(count = 20) {
Text("item $it", modifier = Modifier
.fillMaxWidth()
.padding(16.dp))
}
}
}
tested on devices: SGS10 with android 12 and pixel 3a with android 12
Recently #31 was merged and I've tried it out in hopes of getting a working bottom bar. However to my great despair, placing bottom app bar at a fixed position while is beneficial, still does not allow us to use the APIs that are provided by Scaffold. In my case, Google's scaffold provides a slot for BottomAppBar, Fab and most importantly, allows providing FabPosition. This way fab is placed in a "cradle".
https://developer.android.com/jetpack/compose/layouts/material#fab states that
The bottom placement of the FAB composable is handled internally. You can use the floatingActionButtonPosition parameter to adjust the horizontal position
Internally, BottomBar uses CompositionLocalProvider
which provides
val fabPlacement = LocalFabPlacement.current
which BottomAppBar()
then consumes.
I see that in this file most of the Scaffold's sources are copied and pasted. I think this is undesirable and will lead to the need for changes every time Google makes changes to their scaffolds. Not only that, with current architecture we already need 3 or more scaffold variations to support all use-cases presented by users.
We'll need to come up with a way to support nested Scaffolds or using Material's Scaffolds with Collapsing toolbar component without the need to create more and more scaffold variations for every use-case.
Meanwhile, I'll try to come up with a workaround for BottomBar Fab Position.
Hello.
I'm trying to make a window with button that is always at the bottom. I have column with some items and button at the end. I have added spacer between items and button to fill the remaining space making button pined to bottom. The issue is that this doesn't work with CollapsingToolbar and I have to scroll to reveal the button. I think the issue is that content height is calculated incorrectly when toolbar is initially expanded.
Here is code snippet to reproduce this issue:
CollapsingToolbarScaffold(
modifier = Modifier
.fillMaxSize(),
state = state,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbar = {
val textSize = (18 + (30 - 18) * state.toolbarState.progress).sp
Box(
modifier = Modifier
.background(MaterialTheme.colors.primary)
.fillMaxWidth()
.height(150.dp)
.pin()
)
Text(
text = "Title",
modifier = Modifier
.road(Alignment.CenterStart, Alignment.BottomEnd)
.padding(60.dp, 16.dp, 16.dp, 16.dp),
color = Color.White,
fontSize = textSize
)
Icon(
Icons.Default.ArrowBack,
modifier = Modifier
.pin()
.padding(16.dp),
contentDescription = null
)
}
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Spacer(modifier = Modifier
.fillMaxHeight()
.weight(1f))
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = "Button")
}
}
}
echo "# https-l.facebook.com-" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/Painting-a-picture-entertainment/https-l.facebook.com-.git
git push -u origin main
Hello. This works great for collapsing toolbars, but is there a way to expand/collapse the toolbar when needed?
My use case is that I have a search input on the toolbar and I want to expand the toolbar when keyboard is visible (via LocalWindowInsets.current.ime.isVisible
)
Is it necessary to clip this layout to its bounds? If so, can you at least allow this to be optionally overridden so that a toolbar content such as the one below can show its shadow over the bodyContent?
CollapsingToolbarScaffold(
modifier = Modifier.fillMaxSize(),
state = rememberCollapsingToolbarScaffoldState(),
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbarModifier = Modifier.fillMaxWidth(),
toolbar = {
TopAppBar(
backgroundColor = MaterialTheme.colors.background,
elevation = 16.dp,
modifier = Modifier
.fillMaxWidth()
.height(104.dp) // shrinks to 56.dp
.road(Alignment.BottomStart, Alignment.BottomStart)
.background(Color.White),
) { ... }
Box(modifier = Modifier.size(56.dp)) {
if (showNavigation) {
IconButton(onClick = { }, modifier = Modifier.align(Alignment.Center)) {
Icon(Icons.Default.ArrowBack, contentDescription = null)
}
}
}
},
body = { ... }
)
I have 2 composable screens with CollapsingToolbarScaffold
. When you enter 1st screen the padding on the bottom is like it should be 0.dp
, but when you go from 1st to 2nd screen, it seems that the ScrollStrategy.ExitUntilCollapsed
on the second screen adds some padding around 56.dp
on the bottom. If I use any other scroll strategy the problem is gone. Both screens use ScrollStrategy.ExitUntilCollapsed
.
Update 1 (Workaround):
If you add
Box(modifier = Modifier.fillMaxSize()) {
}
Outside of CollapsingToolbarScaffold, the padding is now gone and it is drawn through the whole screen.
Update 2: Real solution
Don't forget to add a modifier = Modifier.fillMaxSize(), else the scaffold isn't expanded to fill the screen, even though you have enough items.
I noticed for this specific strategy offsetY is always 0. It appears it's missing the call to doScroll
inside of its nestedScroll implementation. Is there another way I'm not seeing it gets set or another way to get the offsetY in exitUntilCollapsed?
If not, I could create a PR to address this issue. Let me know!
ScrollStrategy: ExitUntilCollapsed
Toolbar layout: pinned TopAppbar, parallax Content that contains 4 TabRows๏ผthe first of TabRows contains more than 20 Tabs
Drag to the top until the toolbar fully expands and then start flinging by lifting the finger. The main body will show overscroll effect too long which is very bad for UX.
This issue does not happen with version 2.3.4 which uses Compose 1.1.1, so I suspect that Compose 1.2 changed its behavior regarding nested scrolling mechanism.
The root cause of the issue is the use of the custom NestedScrollConnection
which adjusts the velocity(in onPreFling
callback) to help user fling naturally.
The content is scrollable even when it's 1 item in the list and half of the screen is empty.
How to detect if the content takes the whole screen and make it scrollable only then ?
Hello all.
Here's my situation:
I need to implement a (not compose, so I can't use HorizontalPager
) ViewPager on my screen. Each ViewPager page is a list with an infinite scroll feature implemented. I'm using the ScrollableTabRow composable for tabs.
The screen is all implemented, but I'm facing issues with the scrolling.
CollapsingToolbarScaffold
lib recommends to set verticalScroll(rememberScrollState())
to the parent Column of my content. This works fine, but the ViewPager height doesn't seem to be calculated, making the scroll not happen. After some research, I found that we need to set the height explicitly if I want to use verticalScroll
, but I couldn't make it work.
Here's what I've already tried:
BoxWithConstraints
and set the maxHeight;onGloballyPositioned
modifier;scrollable(rememberScrollState(), Orientation.Vertical)
ViewCompat.setNestedScrollingEnabled(this, true)
to the ViewPager (this works, but the scroll gets no smooth at all so, certainly not an option).Any ideas? Here's my code:
val selectedPage = remember {
mutableStateOf(0)
}
val pagerState = rememberPagerState(initialPage = selectedPage.value)
Surface(modifier = Modifier.fillMaxSize(), color = Color(0xFFF1F3F5)) {
Column {
ScrollableTabRow(
edgePadding = 0.dp,
selectedTabIndex = selectedPage.value,
backgroundColor = Color.Transparent,
modifier = Modifier
.padding(16.dp)
.height(36.dp),
indicator = { },
divider = { }
) {
NativeTab("Tab 1", 0, pagerState, selectedPage)
NativeTab("Tab 2", 1, pagerState, selectedPage)
NativeTab("Tab 3", 2, pagerState, selectedPage)
}
AndroidView(
factory = { context ->
ViewPager(context).also {
it.adapter = CustomPagerAdapter(context)
it.currentItem = selectedPage.value
it.addOnPageChangeListener(object :
ViewPager.OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
selectedPage.value = position
}
override fun onPageScrollStateChanged(state: Int) {
}
})
ViewCompat.setNestedScrollingEnabled(it, true)
}
},
update = {
it.currentItem = selectedPage.value
},
modifier = Modifier.weight(1f)
)
}
}
Any help would be much appreciated.
Thanks
Compose Version: 1.1.1
Library Version: 2.3.0
Kotlin: 1.6.10
androidx.compose.ui.geometry.Offset.getX-impl (Offset.kt:67)
androidx.compose.ui.input.pointer.util.VelocityTracker.calculateVelocity-9UxMQ8M (VelocityTracker.kt:73)
me.onebone.toolbar.RelativeVelocityTracker.reset (RelativeVelocityTracker.java:56)
me.onebone.toolbar.ExitUntilCollapsedNestedScrollConnection.onPreFling-QWom1Mo (ScrollStrategy.kt:229)
androidx.compose.ui.input.nestedscroll.ParentWrapperNestedScrollConnection.onPreFling-QWom1Mo (NestedScrollDelegatingWrapper.kt:182)
androidx.compose.ui.input.nestedscroll.ParentWrapperNestedScrollConnection.onPreFling-QWom1Mo (NestedScrollDelegatingWrapper.kt:181)
androidx.compose.ui.input.nestedscroll.ParentWrapperNestedScrollConnection.onPreFling-QWom1Mo (NestedScrollDelegatingWrapper.kt:181)
androidx.compose.ui.input.nestedscroll.ParentWrapperNestedScrollConnection.onPreFling-QWom1Mo (NestedScrollDelegatingWrapper.kt:181)
androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher.dispatchPreFling-QWom1Mo (NestedScrollModifier.kt:223)
androidx.compose.foundation.gestures.ScrollingLogic.onDragStopped (Scrollable.kt:292)
androidx.compose.foundation.gestures.ScrollableKt$touchScrollable$4$1.invokeSuspend (Scrollable.kt:214)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:106)
androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch (AndroidUiDispatcher.android.kt:81)
androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch (AndroidUiDispatcher.android.kt:41)
androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame (AndroidUiDispatcher.android.kt:68)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:1159)
android.view.Choreographer.doCallbacks (Choreographer.java:983)
android.view.Choreographer.doFrame (Choreographer.java:904)
android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1146)
android.os.Handler.handleCallback (Handler.java:938)
android.os.Handler.dispatchMessage (Handler.java:99)
android.os.Looper.loop (Looper.java:263)
android.app.ActivityThread.main (ActivityThread.java:8292)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:612)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1006)
IntelliJ IDEA imports me.onebone.toolbar.CollapsingToolbarScopeInstance.road
and complains that it's internal when trying to use Modifier.road() or others
IntelliJ IDEA 2021.3 RC (Ultimate Edition)
Build #IU-213.5744.125, built on November 17, 2021
Runtime version: 11.0.13+7-b1751.19 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Linux 5.15.4-arch1-1
GC: G1 Young Generation, G1 Old Generation
Memory: 4096M
Cores: 16
Kotlin: 213-1.5.10-release-949-IJ5744.125
Current Desktop: KDE
CollapsingToolbar v. 2.3.0
I've played around with the sample app and there's an issue if you enable edge to edge with the keyboard. It scrolls the Column
but not enough to keep it in view. Sample code:
class MainActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
CollapsingToolbarTheme { MainScreen() }
}
}
}
@Composable
fun MainScreen() {
CollapsingToolbarScaffold(
modifier = Modifier
.statusBarsPadding()
.imePadding()
.fillMaxSize(),
state = rememberCollapsingToolbarScaffoldState(),
scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed,
toolbar = {
TopAppBar(
title = { Text(text = "Title") },
modifier = Modifier.height(56.dp),
)
}
) {
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.navigationBarsPadding(),
) {
repeat(100) {
TextField(
value = "Item $it",
onValueChange = {},
modifier = Modifier.padding(8.dp)
)
}
}
}
}
I'm sure it has to do with the body offset when toolbar is expanded. LazyColumn
is worse, losing the focus when keyboard is showing (that's for sure a compose issue). Any thoughts on this?
I use ScrollStrategy.ExitUntilCollapsed within my view that holds the collapseble tool bar and a list.
when i scroll all the way to the bottom and slowly scroll back up, the tool bar is expanded according to ScrollStrategy.EnterAlways
Added the option to remember the progress, sometimes scrolling the app stays in "moving state" recomposing the content
val state = rememberCollapsingToolbarScaffoldState()
val progress = state.toolbarState.progress
val topHeight = (54 * progress).dp
Timber.v("toolbar progress $progress topPadding:$topHeight ")
CollapsingToolbarScaffold(
toolbar = {
val defaultFontSize = 54
val smallFontSize = 20
val textSize =
(smallFontSize + (defaultFontSize - smallFontSize) * state.toolbarState.progress).sp
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.primary)
.fillMaxWidth()
.height(80.dp)
.pin()
)
TransactionHeader(balance = "325,57 โฌ", {}, {}, {}, {},
balanceTextSize = textSize,
topBarAlpha = progress,
topHeight = topHeight
)
},
modifier = Modifier.fillMaxWidth(),
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
state = state
) {
LazyListWithItems()
}
}
the progress
keep updating even when the screen is not touched.
sometimes happens after doing overscroll or just playing around, and this issue happens, any suggestions? Thanks
Hey,
I have an issue with ExitUntilCollapsed scroll strategy. Somehow, the collapsing toolbar makes weird jump to top when navigating to next page and back. The issue can be seen in this video: https://streamable.com/j13xjn
However, if I change the scroll strategy to something else, then there is no jump after navigating back. The issue disappears also if I don't use collapsing toolbar at all.
val toolbarState = rememberCollapsingToolbarScaffoldState()
Surface {
CollapsingToolbarScaffold(
modifier = Modifier.fillMaxSize(),
state = toolbarState,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
enabled = true,
toolbar = {
SmallTopAppBar(
modifier =
Modifier.padding(top = 38.dp).graphicsLayer {
alpha = 1 - state.toolbarState.progress
},
title = { Text(title, overflow = TextOverflow.Ellipsis, maxLines = 1) },
navigationIcon = {
IconButton(onClick = { navigateBack() }) {
Icon(
Icons.Default.ArrowBack,
contentDescription = "backButton",
)
}
},
)
Box(
modifier =
Modifier.fillMaxSize().padding(bottom = 8.dp).graphicsLayer {
alpha = state.toolbarState.progress
}
) { Box(modifier = Modifier.height(200.dp).background(Color.Red)) }
},
) {
Scaffold { LazyColumn(modifier = Modifier.fillMaxWidth().padding(it)) { Content() } }
}
}
If scroll is more than 50% than for positive scroll show toolbar and negative scroll hide toolbar.
I don't want toolbar to be partially visible.
Please tell me how to achieve this behavior.
Hi there!
Great library, thanks so much for building this out. I'm currently trying to implement this in my application, and can't seem to figure out how to get the CollapsingToolbar to work with a regular Compose Scaffold.
I have a TopAppBar, BottomAppBar, and FloatingActionButton, so using the Scaffold included by default in Compose is somewhat necessary. Is there a way to wrap the TopAppBar in a CollapsingToolbar and get it to scroll correctly? I can't seem to get it to work.
Here's what I have so far:
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
val collapsingToolbarState = rememberCollapsingToolbarState()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
CollapsingToolbar(
collapsingToolbarState = collapsingToolbarState
) {
TopAppBar(
backgroundColor = MaterialTheme.colors.background,
title = {
Text(text = "TopAppBar")
}
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.parallax(0.5f)
.graphicsLayer {
alpha = collapsingToolbarState.progress
}
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null
)
}
}
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
FloatingActionButton(onClick = { /*TODO*/ }) {
Text("X")
}
},
drawerContent = {
Text(text = "drawerContent")
},
bottomBar = {
BottomNavigation(
backgroundColor = MaterialTheme.colors.background
) {
.....
}
},
) {
LazyColumn(
modifier = Modifier.fillMaxWidth()
) {
items(100) {
Text(
text = "Item $it",
modifier = Modifier.padding(8.dp)
)
}
}
}
However, the expanding/collapsing doesn't seem to work. Here's a video:
Thanks in advance for the help!
My toolbar contains pretty large numbers of elements so its performance is especially important for me. Here is my toolbar:
On the GIF you can see performance drop when scroll occur. It's important to node that the GIF is not compressed.
Here is the code of my toolbar layout:
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.FloatRange
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.contentColorFor
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import me.onebone.toolbar.ui.theme.CollapsingToolbarTheme
class PerformanceTestActivity: ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CollapsingToolbarTheme {
Surface(color = MaterialTheme.colors.background) {
MyScaffold(
modifier = Modifier.fillMaxSize(),
)
}
}
}
}
}
@Composable
private fun MyScaffold(
modifier: Modifier = Modifier,
) {
MyAppBarScaffold(
modifier = modifier.fillMaxSize(),
) {
val timestamp = System.currentTimeMillis()
LazyColumn {
items(100) {
val timestamp2 = System.currentTimeMillis()
Text(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
text = "I'm item $it"
)
Log.d("perf list item", "list item draw speed = ${System.currentTimeMillis() - timestamp2}")
}
}
Log.d("perf list", "list draw speed = ${System.currentTimeMillis() - timestamp}")
}
}
@Composable
private fun MyAppBarScaffold(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val collapsingToolbarScaffoldState = rememberCollapsingToolbarScaffoldState()
CollapsingToolbarScaffold(
modifier = modifier,
state = collapsingToolbarScaffoldState,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbarModifier = Modifier.shadow(AppBarDefaults.TopAppBarElevation),
toolbar = {
val timestamp = System.currentTimeMillis()
val progress = collapsingToolbarScaffoldState.toolbarState.progress
// val progress = 1f
Surface(
modifier = Modifier
.height(IntrinsicSize.Min)
// .height(300.dp)
.parallax(0.5f), // TODO: Affects performance
color = MaterialTheme.colors.primarySurface,
elevation = AppBarDefaults.TopAppBarElevation,
) {
MyAppBarContent(
progress = progress,
)
}
MyExpandedAppBar(
modifier = Modifier
.road(Alignment.BottomStart, Alignment.BottomStart), // TODO: Affects performance
progress = progress, // TODO: Affects performance
// progress = 1f,
)
// Collapsing toolbar collapses its size as small as the that of a smallest child (this)
MyCollapsedAppBar(
modifier = Modifier.clickable(onClick = { }), // TODO: Affects performance
progress = progress, // TODO: Affects performance
// progress = 1f,
)
Log.d("perf", "toolbar draw speed = ${System.currentTimeMillis() - timestamp}, progress = $progress")
},
body = content
)
}
@Composable
private fun MyExpandedAppBar(
modifier: Modifier = Modifier,
@FloatRange(from = 0.0, to = 1.0) progress: Float,
) {
Log.d("redraw", "expanded bar redrawing")
MyAppBar(
modifier = modifier,
title = {
Log.d("redraw", "expanded bar title redrawing")
val progressReversed = 1f - progress
Text(
modifier = Modifier.alpha(progressReversed.configureProgress(0.5f)),
text = stringResource(R.string.app_name),
color = MaterialTheme.colors.onPrimary
)
},
actions = {
Log.d("redraw", "expanded bar actions redrawing")
IconButton(
modifier = Modifier.alpha(progress.configureProgress(0.5f)),
onClick = { }
) {
Icon(imageVector = Icons.Filled.Share, contentDescription = null)
}
}
)
}
@Composable
private fun MyCollapsedAppBar(
modifier: Modifier = Modifier,
@FloatRange(from = 0.0, to = 1.0) progress: Float,
) {
Log.d("redraw", "collapsed bar redrawing")
val popupExpanded = remember { mutableStateOf(false) }
val popupOptions = arrayOf("option #1", "option #2")
MyAppBar(
modifier = modifier,
title = {
Log.d("redraw", "collapsed bar title redrawing")
Text(
modifier = Modifier.alpha(progress),
text = "Collapsed app bar",
color = MaterialTheme.colors.onPrimary
)
},
actions = {
Log.d("redraw", "collapsed actions redrawing")
IconButton(onClick = { popupExpanded.value = true }) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = null
)
}
}
)
}
@Composable
private fun MyAppBarContent(
modifier: Modifier = Modifier,
@FloatRange(from = 0.0, to = 1.0) progress: Float,
) {
Log.d("redraw", "content redrawing")
Box(
modifier = modifier
.fillMaxWidth()
.alpha(progress.configureProgress(0.5f)),
contentAlignment = Alignment.Center
) {
Log.d("redraw", "image redrawing")
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = null,
contentScale = ContentScale.Crop
)
Row(
modifier = Modifier
.padding(horizontal = 16.dp, vertical = MaterialAppBarHeight)
.fillMaxSize(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Log.d("redraw", "tile row redrawing")
MyTile(
title = "title #1",
value = "123"
)
MyTile(
title = "title #2",
value = "456"
)
}
}
}
@Composable
private fun MyTile(
modifier: Modifier = Modifier,
title: String,
value: String,
) {
Log.d("redraw", "tile redrawing")
val fontScale = LocalContext.current.resources.configuration.fontScale
Column(
modifier = modifier.height(MyStatisticsTileHeight.times(fontScale)),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(vertical = 8.dp),
text = title
)
Box(
modifier = Modifier
.padding(bottom = 8.dp)
.aspectRatio(1f)
.border(
width = MyStatisticsTileBorderWidth,
color = MaterialTheme.colors.onPrimary,
shape = RoundedCornerShape(8.dp)
)
.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
modifier = Modifier
.padding(horizontal = MyStatisticsTileBorderWidth.times(2)),
text = value,
textAlign = TextAlign.Center
)
}
}
}
@Composable
private fun MyAppBar(
modifier: Modifier = Modifier,
title: @Composable () -> Unit,
actions: @Composable RowScope.() -> Unit = {},
) {
TopAppBar(
modifier = modifier.height(MaterialAppBarHeight),
title = title,
actions = actions,
backgroundColor = Color.Transparent,
contentColor = contentColorFor(MaterialTheme.colors.primarySurface),
elevation = 0.dp
)
}
private val MyStatisticsTileHeight = 118.dp
private val MyStatisticsTileBorderWidth = 5.dp
private val MaterialAppBarHeight = 56.dp
/**
* Applies configurations on value that represent a progress (e.g. animation)
*
* @param startAt Sets the starting point for the value. The resulting progress will begin
* to increase only when the original progress value reaches passed value.
*/
fun Float.configureProgress(@FloatRange(from = 0.0, to = 1.0) startAt: Float): Float {
val start = (1f - startAt).coerceAtLeast(0f)
val multiplier = 1f / start
return (this - start) * multiplier
}
I added some logs to figure out what parts of UI affected by recomposition. It turned out that they all were affected after any change in the toolbar state. Even if I remove alpha modifier the recomposition process still affect all toolbar composables. I think this behaviour directly contradicts the basic principle of Compose - doing recomposition only if state of composable has changed
In my case there is no need to recompose toolbars (only they titles). So there must a way not to trigger recomposition of all tolbar content
I'm still in research of this problem. But it's important to discuss this problem.
CollapsingToolbarScaffold
doesn't seem to report scroll events until the toolbar is fully collapsed when a nested scroll connection is provided. Is this expected? Does Compose UI require special handling from CollapsingToolbarScaffold
?
CollapsingToolbarScaffold(
modifier = Modifier.nestedScroll(nestedScrollConnection)
)
Hello, I can't expand or collapse the toolbar programmatically. It works nicely when I scroll a lazyColumn
inside it but
collapsingToolbarScaffoldState.toolbarState.expand()
or collapsingToolbarScaffoldState.toolbarState.collapse()
never works. I know these are in experimental state, so decided to scroll it manually but it is not working either. I will share my code below and any help would be much appreciated.
val collapsingToolbarScaffoldState = rememberCollapsingToolbarScaffoldState()
CollapsingToolbarScaffold(
state = collapsingToolbarScaffoldState,
toolbar = {
basePageComposableParams.collapsableToolbarContent.value.invoke()
},
modifier = Modifier,
scrollStrategy = ScrollStrategy.EnterAlways,
) {
Scaffold(
floatingActionButton = basePageComposableParams.floatingActionButtonContent.value
) {
ModalBottomSheetLayout(
modifier = Modifier,
sheetState = sheetState,
sheetContent = {
basePageComposableParams.bottomSheetContent.value.invoke()
}) {
content()
}
}
}
I call the expand and collapse functions in an onClick callback of a FAB
val crScope = rememberCoroutineScope()
floatingActionButtonContent.value = @Composable {
FloatingActionButton(
onClick = {
crScope.launch {
focusRequester.requestFocus()
collapsingToolbarScaffoldState.toolbarState.expand()
}
},
) {
Icon(Icons.Filled.Search, contentDescription = "search")
}
}
Hello
I used a code snippet from #19 with EnterAlwaysCollapsed strategy. bottom button is displayed outside the screen, if start scrolling up - it appears
I think the reason is that always used the max height body, without dependency on toolbar height
CollapsingToolbarScaffold(
modifier = Modifier
.fillMaxSize(),
state = state,
scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed,
toolbar = {
val textSize = (18 + (30 - 18) * state.toolbarState.progress).sp
Box(
modifier = Modifier
.background(MaterialTheme.colors.primary)
.fillMaxWidth()
.height(150.dp)
.pin()
)
Text(
text = "Title",
modifier = Modifier
.road(Alignment.CenterStart, Alignment.BottomEnd)
.padding(60.dp, 16.dp, 16.dp, 16.dp),
color = Color.White,
fontSize = textSize
)
Icon(
Icons.Default.ArrowBack,
modifier = Modifier
.pin()
.padding(16.dp),
contentDescription = null
)
}
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.DarkGray)
.height(64.dp)
) {}
Spacer(modifier = Modifier
.fillMaxHeight()
.weight(1f))
Button(
onClick = { /*TODO*/ },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
) {
Text(text = "Button")
}
}
}
Although the user flings the content all the way to the top and there is a left velocity that CollapsingToolbar can consume, it does not consume fling.
I can implement fling behavior by overriding onPreScroll()
and onPostScroll()
callbacks in NestedScrollConnection. However, the problem is that as of Jetpack Compose 1.0.0-rc01 it calculates fling velocity by relative position of the content (For the actual source code calculating the velocity, refer to here and here). As an offset of content moves as the toolbar is expanded/collapsed, the velocity that is calculated inside a child(moving) layout does not provide a valid velocity as a whole resulting in buggy fling behavior because the change in relative position hardly matches the global position change in screen surface.
Jetpack Compose basically utilizes event bubble(where event is passed from child through its parents) while we can capture an event before the child processes it using Initial PointerEventPass. However using PointerEventPass.Initial
type will pay high implementation cost as there is an only small set of high level API with Initial passing type.
The traditional Android UI components which support nested scrolling such as RecyclerView and NestedScrollView calculate velocity using relative position, but they adjust their offset when its relative position to the screen has changed as we can see at here and here.
At the moment, I could not find a method to implement flinging behavior correctly, I will leave this as an issue until a valid way is found.
Hello!
I have a screen that the appbar changes its elevation through the scroll state, but it don't work on my tries, is possible to do it?
The problem I have found is that for the collapsing toolbar to work, the height of the composable is taken as expanded height, and that height is fixed in the examples because there is always an image or a background (Box) of fixed height, but in my case, the height has to be variable because the TopAppBar has to modify its height with the scroll and increase or decrease the elevation.
This case have a crash
setContentView(ComposeView(this).apply {
setContent {
CollapsingToolbarTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
MainScreen()
}
}
}
})
echo "# https-l.facebook.com-" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/Painting-a-picture-entertainment/https-l.facebook.com-.git
git push -u origin main
Expected:
Currently:
Implementation
@OptIn(ExperimentalPagerApi::class)
@Composable
internal fun MainScreen() {
val state = rememberCollapsingToolbarScaffoldState()
val tabData = listOf(
"MUSIC" to Icons.Filled.Home,
"MARKET" to Icons.Filled.ShoppingCart
)
val pagerState = rememberPagerState(
pageCount = tabData.size,
initialOffscreenLimit = 1,
infiniteLoop = false,
initialPage = 0,
)
val tabIndex = pagerState.currentPage
val coroutineScope = rememberCoroutineScope()
CollapsingToolbarScaffold(
modifier = Modifier
.fillMaxSize(),
state = state,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbar = {
Box(
modifier = Modifier
.height(400.dp)
.fillMaxWidth()
.background(Color.Blue)
)
TabRow(
selectedTabIndex = tabIndex,
indicator = { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[tabIndex])
)
},
modifier = Modifier.road(
whenCollapsed = Alignment.CenterStart,
whenExpanded = Alignment.BottomEnd
)
) {
tabData.forEachIndexed { index, pair ->
Tab(selected = tabIndex == index, onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
}, text = {
Text(text = pair.first)
}, icon = {
Icon(imageVector = pair.second, contentDescription = null)
})
}
}
}
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
) {
items(100) {
Text(
text = "Item $it",
modifier = Modifier.padding(8.dp)
)
}
}
}
}
`
I have a navigation bar below, when I move the Toolbar, the navigation bar is also moved, I don't know how to handle it, can you help me?
Hello!
I just launched your app and noticed that there are lags. It's impossible to collapse the toolbar completely by making one scroll with your finger. Toolbar animation will stop somewhere in the middle (depends on your scroll velocity). You need to repeat one more scroll with your finger and toolbar will be collapsed. Only after these actions you are able to scroll the main content.
So, from this moment you can collapse the toolbar by making one scroll with your finger and the main content will be scrolled as well.
Do you have any idea, how to avoid scrolling lags on the first screen launch?
Full Log here: https://pastebin.com/wfmWV4bq
Impl Code:
val headerBg =
"https://static.vecteezy.com/system/resources/previews/001/887/504/non_2x/light-blue-gradient-blur-layout-vector.jpg"
val state = rememberCollapsingToolbarScaffoldState()
CollapsingToolbarScaffold(
modifier = Modifier.fillMaxWidth(),
state = state,
toolbar = {
Box(
modifier = Modifier.parallax(0.5f)
) {
Image(
painter = rememberCoilPainter(
request = headerBg,
fadeIn = true,
),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier.height(300.dp)
)
HomeHeader()
}
},
scrollStrategy = ScrollStrategy.ExitUntilCollapsed
) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
item {
Text(
text = "Test",
style = TextStyle(
color = androidx.compose.ui.graphics.Color.Black,
fontSize = 14.sp,
letterSpacing = (0.2f).sp
)
)
}
itemsIndexed(tripList) { pos, data ->
HomeTripItem(homeTripModel = data)
Spacer(modifier = Modifier.height(24.dp))
}
}
}
Hi,
I have rather big tool bar that holds an image pager (Google Accompanist pager) and the toolbar does not collapsed while swiping up.
I tested it on simpler toolbar as well.
I came across the following issue. I use compose navigation. When I navigate back and the remembered toolbar height value is restored, maxHeight is set to initial Int.MAX_VALUE which results in really small progress value and causes components that are using progress value to flicker when you navigate back (video included).
Hey @onebone, would it be possible to snap the header to its nearest edge when a scroll is released?
Hi, noticed this IndexOutOfBoundsException crash on crashlytics after updating the lib to 2.3.4.
Fatal Exception: java.lang.IndexOutOfBoundsException: Index 1, size 0
at androidx.compose.foundation.lazy.layout.MutableIntervalList.checkIndexBounds(MutableIntervalList.java:177)
at androidx.compose.foundation.lazy.layout.MutableIntervalList.get(MutableIntervalList.java:160)
at androidx.compose.foundation.lazy.LazyListItemsSnapshot.getKey(LazyListItemsSnapshot.java:80)
at androidx.compose.foundation.lazy.LazyListItemProviderImpl.getKey(LazyListItemProviderImpl.java:113)
at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScopeImpl.java:116)
at androidx.compose.foundation.lazy.LazyMeasuredItemProvider.getAndMeasure-ZjPyQlc(LazyMeasuredItemProvider.java:47)
at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-7Xnphek(LazyListMeasureKt.java:151)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke-0kLqBqw(LazyListKt.java:304)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke(LazyListKt.java:197)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke-0kLqBqw(LazyLayoutKt.java:74)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke(LazyLayoutKt.java:70)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(LayoutNodeSubcompositionsState.java:590)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.java:44)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke-3p2s80s(AndroidOverscrollKt.java:535)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke(AndroidOverscrollKt.java:534)
at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifierImpl.java:285)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.java:53)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke-3p2s80s(AndroidOverscrollKt.java:519)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke(AndroidOverscrollKt.java:518)
at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifierImpl.java:285)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.java:53)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(SimpleGraphicsLayerModifier.java:405)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.java:53)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.java:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.java:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.java:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.java:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.java:1366)
at androidx.compose.foundation.layout.RowColumnImplKt$rowColumnMeasurePolicy$1.measure-3p2s80s(RowColumnImplKt.java:89)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.java:44)
at androidx.compose.foundation.layout.PaddingModifier.measure-3p2s80s(PaddingModifier.java:364)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.java:53)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.java:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.java:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.java:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.java:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.java:1366)
at androidx.compose.foundation.layout.RowColumnImplKt$rowColumnMeasurePolicy$1.measure-3p2s80s(RowColumnImplKt.java:89)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.java:44)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.java:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.java:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.java:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.java:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.java:1366)
at me.onebone.toolbar.CollapsingToolbarScaffoldKt$CollapsingToolbarScaffold$2.measure-3p2s80s(CollapsingToolbarScaffoldKt.java:149)
at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.java:44)
at androidx.compose.foundation.layout.FillModifier.measure-3p2s80s(FillModifier.java:658)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.java:53)
at androidx.compose.foundation.layout.PaddingValuesModifier.measure-3p2s80s(PaddingValuesModifier.java:417)
at androidx.compose.ui.node.ModifiedLayoutNode.measure-BRTryo0(ModifiedLayoutNode.java:53)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1428)
at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.java:1427)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.java:66)
at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui_release(LayoutNode.java:1427)
at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.java:94)
at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.java:75)
at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.java:1366)
at com.google.accompanist.insets.ui.ScaffoldKt$ScaffoldLayout$1$1$1.invoke(ScaffoldKt.java:279)
at com.google.accompanist.insets.ui.ScaffoldKt$ScaffoldLayout$1$1$1.invoke(ScaffoldKt.java:194)
at androidx.compose.ui.layout.MeasureScope$layout$1.placeChildren(MeasureScope.java:70)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure$1.placeChildren(LayoutNodeSubcompositionsState.java:602)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.java:968)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.java:953)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutSnapshotReads$ui_release(OwnerSnapshotObserver.java:52)
at androidx.compose.ui.node.LayoutNode.layoutChildren$ui_release(LayoutNode.java:953)
at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.java:938)
at androidx.compose.ui.node.InnerPlaceable.placeAt-f8xVGno(InnerPlaceable.java:79)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.java:370)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.java:161)
at androidx.compose.ui.node.OuterMeasurablePlaceable.access$placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.java:28)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.java:149)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.java:148)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutModifierSnapshotReads$ui_release(OwnerSnapshotObserver.java:59)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeAt-f8xVGno(OuterMeasurablePlaceable.java:148)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.java:370)
at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50$default(Placeable.java:203)
at androidx.compose.foundation.layout.BoxKt.placeInBox(BoxKt.java:186)
at androidx.compose.foundation.layout.BoxKt.access$placeInBox(BoxKt.java:1)
at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1$measure$2.invoke(BoxKt.java:126)
at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1$measure$2.invoke(BoxKt.java:125)
at androidx.compose.ui.layout.MeasureScope$layout$1.placeChildren(MeasureScope.java:70)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.java:968)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.java:953)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutSnapshotReads$ui_release(OwnerSnapshotObserver.java:52)
at androidx.compose.ui.node.LayoutNode.layoutChildren$ui_release(LayoutNode.java:953)
at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.java:938)
at androidx.compose.ui.node.InnerPlaceable.placeAt-f8xVGno(InnerPlaceable.java:79)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeWithLayer(Placeable.java:393)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeWithLayer$default(Placeable.java:266)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier$measure$1.invoke(SimpleGraphicsLayerModifier.java:407)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier$measure$1.invoke(SimpleGraphicsLayerModifier.java:406)
at androidx.compose.ui.layout.MeasureScope$layout$1.placeChildren(MeasureScope.java:70)
at androidx.compose.ui.node.ModifiedLayoutNode.placeAt-f8xVGno(ModifiedLayoutNode.java:101)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeWithLayer-aW-9-wM(Placeable.java:396)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.java:163)
at androidx.compose.ui.node.OuterMeasurablePlaceable.access$placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.java:28)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.java:149)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.java:148)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutModifierSnapshotReads$ui_release(OwnerSnapshotObserver.java:59)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeAt-f8xVGno(OuterMeasurablePlaceable.java:148)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeRelativeWithLayer(Placeable.java:385)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeRelativeWithLayer$default(Placeable.java:246)
at androidx.compose.ui.layout.RootMeasurePolicy$measure$2.invoke(RootMeasurePolicy.java:43)
at androidx.compose.ui.layout.RootMeasurePolicy$measure$2.invoke(RootMeasurePolicy.java:39)
at androidx.compose.ui.layout.MeasureScope$layout$1.placeChildren(MeasureScope.java:70)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.java:968)
at androidx.compose.ui.node.LayoutNode$layoutChildren$1.invoke(LayoutNode.java:953)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutSnapshotReads$ui_release(OwnerSnapshotObserver.java:52)
at androidx.compose.ui.node.LayoutNode.layoutChildren$ui_release(LayoutNode.java:953)
at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.java:938)
at androidx.compose.ui.node.InnerPlaceable.placeAt-f8xVGno(InnerPlaceable.java:79)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.java:370)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.java:161)
at androidx.compose.ui.node.OuterMeasurablePlaceable.access$placeOuterWrapper-f8xVGno(OuterMeasurablePlaceable.java:28)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.java:149)
at androidx.compose.ui.node.OuterMeasurablePlaceable$placeAt$1.invoke(OuterMeasurablePlaceable.java:148)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.java:2101)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.java:110)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.java:78)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutModifierSnapshotReads$ui_release(OwnerSnapshotObserver.java:59)
at androidx.compose.ui.node.OuterMeasurablePlaceable.placeAt-f8xVGno(OuterMeasurablePlaceable.java:148)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.java:31)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeRelative(Placeable.java:359)
at androidx.compose.ui.layout.Placeable$PlacementScope.placeRelative$default(Placeable.java:179)
at androidx.compose.ui.node.LayoutNode.place$ui_release(LayoutNode.java:811)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.java:278)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.java:38)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.java:208)
at androidx.compose.ui.platform.AndroidComposeView.onMeasure(AndroidComposeView.java:806)
at android.view.View.measure(View.java:27129)
at androidx.compose.ui.platform.AbstractComposeView.internalOnMeasure$ui_release(AbstractComposeView.java:298)
at androidx.compose.ui.platform.AbstractComposeView.onMeasure(AbstractComposeView.java:285)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:145)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:27129)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7980)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:197)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:1278)
at android.view.View.measure(View.java:27129)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:4528)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:3220)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3525)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2911)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10458)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1108)
at android.view.Choreographer.doCallbacks(Choreographer.java:866)
at android.view.Choreographer.doFrame(Choreographer.java:797)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1092)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8669)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Hey,
The library is amazing, but I have an issue that I don't know how to solve.
I want to put the Button
inside CollapsingToolbarScaffold
, but I want it to be always shown, no matter the state of CollapsingToolbarScaffold
. Currently if you put it inside it won't show until the state of the toolbar collapses.
1 solution I tried is to put the Button
outside of CollapsingToolbarScaffold
, but then you have a button that is not part of the content that you want to show.
For example if you have an AnimatedNavHost
inside the content of CollapsingToolbarScaffold
to manage the screens, the button inside those screens is different, but won't be shown at the bottom of the screen (over the content), but it will be shown at the bottom of the content.
To summarize, is there any way to "flag" a composable, so that it is not influenced by the scrolling state, but is independent and still part of the CollapsingToolbarScaffold
content?
Hi
when running this code, where state is the LazyListState of the LazyColumn which is the body, the CollapsingToolbarScaffold doesn't collapse and the LazyColumn scrolls behind it.
Thanks for the help
coroutineScope.launch {
delay(500)
state.animateScrollToItem(issue.comments.size - 1)
}
Hi, I am using the ExitUntilCollapsed scroll strategy for my project, and it works well.
My toolbar has a navigation icon.
Currently, the entire toolbar (including its navigation icon) gets its visibility changed when scrolling. I want the toolbar's navigation icon to always be displayed, and then have the remaining toolbar elements (background, title, etc.) animate its visibility when scrolling, which is the case already.
Is this possible to do?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.