I saw this app, which I thought to be very clean and well designed, so I tried using it to practice, but I had some issues.
They used a collapsing toolbar, which they made seamless with the status bar, but the toolbar doesn't collapse when scrolled, it just scrolls like a regular view, or composeable in jetpack compose.
And the toolbar scrolls when you swipe on it.
Here's an Image of it;
The issue I'm facing now is;
The collapsing toolbar doesn't scroll when I swipe on it, I tried giving the collapsing toolbar a .verticalScrollState, but it crashes the app.
And Also, when I scroll the elements (views) at the bottom of my App (which is the only side that scrolls, because the toolbar above doesn't respond), I don't get the same motion I get from the original App, mine appears as though the elements at the bottom is overlapping the toolbar, but I want everything to scroll upwards seamlessly.
So please (1) How do I make the collapsing toolbar scrollable, (11) How do I make everything scroll upwards seamlessly.
Here's my Collapsing toolbar code;
first I added a the gradle dependency
implementation "me.onebone:toolbar-compose:2.3.2"
#Composable
fun ContactCollapsingToolbar(
onContactsCardClick: (Int) -> Unit
) {
val collapsingToolbarState = rememberCollapsingToolbarScaffoldState()
CollapsingToolbarScaffold(
modifier = Modifier,
state = collapsingToolbarState,
scrollStrategy = ScrollStrategy.EnterAlwaysCollapsed,
toolbar = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.pin()
.background(
brush = Brush.linearGradient(
colors = listOf(
MaterialTheme.colors.primary,
White
)
)
)
)
Image(
painter = painterResource(id = R.drawable.fc4_self_massage),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(RoundedCornerShape(bottomEnd = 45.dp))
//alpha =
)
Text(
text = stringResource(id = R.string.contact_categories),
style = TextStyle(color = MaterialTheme.colors.onPrimary) + MaterialTheme.typography.h1,
modifier = Modifier.padding(16.dp),
textAlign = TextAlign.Start
)
}
) {
ContactsContentScreen(onContactsCardClick)
}
}
I'll sinerely appreciate any help, whether code, instructions, or directing me somewhere.
Thanks to you in advance.
Related
I want to display a sticky bar at the bottom of the screen to show dynamic or static data similar to the bars in the image below. Can this be achieved with BottomSheetScaffold on screen load without user interaction? If not, how else can this be implemented?
EDIT: I want to achieve this in Jetpack Compose.
This UI collection might be a good starting point for you to see which elements/classes you need. It looks like you're depicting a BottomSheet element? Maybe an AndroidSweetSheet element - Both of these look like they'd suit your purpose. You don't need the animations or can turn off the resizable, if you want them static. I'd start there with that huge collection of curated UI elements/widgets anyway, see if those two, or perhaps some other of the many UI element better fits your purpose.
For example, this is the AndroidSweetSheet: (1)
And heres the BottomSheet (2):
Let the page load (lots of example images so may take a little while depending on your connection/CPU) but theres several that look like what you want. May have extras that you can turn off, such as the resizable nature of that AndroidSweetSheet, you can have it appear rather than slide up & scroll
Just having a look through that UI elements page (for my own app purposes) I noticed this one 3 which might suit your purposes - imagine having a star pop up instead of the play icon, and when the user presses the star, it reveals your "Go premium" panel instead of the music player widget:
OP commented that they're specifically looking for jetpack compose - what about this bottom-sheet?
Although this example looks very bare-bones, the structure looks pretty close to what you're describing in yout text/images:
You'd probably have the pop-up controlled so that it pops-up when you want it to, instead of the user clicking a button.
Edits: some more examples
This code tutorial also suggests building a bottom sheet using jetpack compose.
I'd think you'd probably want your "go premium" box to be modal, and this page does just that, again with jetpack compose - looks prettier, too!
You can consider this one where you can utilize LaunchedEffect with a Unit key to show the bottom sheet immediately on first composition, and closing it using an Icon placed at the end of the gray "header".
All these codes are copy-and-paste-able so you can run it with no issues.
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun MyScreenAutoShowBottomSheet() {
val contentBackground = Color(0xFFa593b8)
val sheetPeekHeight = 40.dp
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
LaunchedEffect(key1 = Unit){
bottomSheetScaffoldState.bottomSheetState.expand()
}
val coroutineScope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetBackgroundColor = contentBackground,
sheetElevation = 0.dp,
sheetPeekHeight = sheetPeekHeight,
sheetContent = {
Column(
modifier = Modifier
.padding(top = sheetPeekHeight)
.wrapContentHeight()
.fillMaxWidth()
.background(Color.DarkGray)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
modifier = Modifier.padding(8.dp),
text = "Go Premium"
)
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.End
) {
Icon(
modifier = Modifier
.clickable {
coroutineScope.launch {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
},
imageVector = Icons.Default.Add,
contentDescription = null
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color(0xFF4fc992))
)
}
},
floatingActionButton = {
FloatingActionButton(
backgroundColor = Color(0xFF4a6ebd),
shape = CircleShape,
onClick = {},
) {
Icon(imageVector = Icons.Filled.Add, contentDescription = "icon")
}
}
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(contentBackground)
)
}
}
A simpler solution without the use of sheets is to use a Box composable with the last element having a Modifier.align(Alignment.BottomCenter) modifier:
Box(
modifier = Modifier.fillMaxSize()
) {
//Main screen contents
Column() {...}
...
Row (
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.height(50.dp)
.background(Color(0xFF4fc992))
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
//Bottom bar contents
}
}
I am struggling with the jetpack compose LazyColumn and the stickyHeader functionality. Basically the static view works well, but once I start scrolling, the items would go over the sticky headers, the scrolling starts a weird behaviour and the last item would never be visible as the scrolling always bounces back.
Here's how it looks like:
Here's the composable:
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun CollectionsScreen(
collectionsLive: LiveData<List<CollectionsView>>,
onCollectionChanged: (ICalCollection) -> Unit
/* some more hoisted functions left out for simplicity */
) {
val list by collectionsLive.observeAsState(emptyList())
val grouped = list.groupBy { it.accountName ?: it.accountType ?: "Account" }
LazyColumn(
modifier = Modifier.padding(8.dp)
) {
item {
Text(
stringResource(id = R.string.collections_info),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 16.dp)
)
}
grouped.forEach { (account, collectionsInAccount) ->
stickyHeader {
Text(
account,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(
top = 16.dp,
start = 8.dp,
end = 16.dp,
bottom = 8.dp
)
)
}
items(
items = collectionsInAccount,
key = { collection -> collection.collectionId }
) { collection ->
CollectionCard(
collection = collection,
allCollections = list,
onCollectionChanged = onCollectionChanged,
/* some more hoisted functions left out for simplicity */
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.animateItemPlacement()
.combinedClickable(
//onClick = { onCollectionClicked(collection) }
)
)
}
}
}
}
I am really not sure what is causing this issue as the code itself is pretty straightforward from the example provided in the documentation. Only the CollectionCard itself is a more complex structure.
I have also tried removing the header text (the first item) and removed the Modifier.animateItemPlacement() for the card, but with no difference, the problem stays the same...
The composable itself is used in a Compose View within a Fragment, but there is no nested scrolling.
Do you have any idea what could cause this strange behaviour? Or might this be a bug when using cards within the LazyColumn with sticky headers?
UPDATE:
It seems like the problem nothing to do with the stickyHeader, but somehow with the LazyColumn. If I replace the "stickyHeader" just with "item", the problem still persists... Only when I replace the lazyColumn with a column it would work. But I assume that there must be a solution for this problem...
Setting the stickyHeader background color will help.
stickyHeader {
Text(
"text",
modifier = Modifier.padding(
top = 16.dp,
start = 8.dp,
end = 16.dp,
bottom = 8.dp
)
.background(colorResource(id = R.color.white))
)
}
I don't know if you solved it yet, but try to fillMaxWidth and set the background. This code worked for me.
Text(
account,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(
top = 16.dp,
start = 8.dp,
end = 16.dp,
bottom = 8.dp
)
.fillMaxWidth()
.background(MaterialTheme.colors.background)
)
In general, if you are using Material or Material3 theming, you can wrap your stickyHeader content in a Surface to automatically make it non-transparent with your theme's standard (or customized) coloring scheme. Surface lets you raise the stickyHeader above your table's other contents.
stickyHeader {
Surface(Modifier.fillParentMaxWidth()) {
Text("Header")
}
}
You can customize the Surface at your heart's desire.
I'd create another issue for the bounciness problem, it looks like a separate concern.
Just provide a background for the sticky header.
I want to place text at the bottom of a card and blur the text area with a translucent color.
This is what I have so far.
The design I am trying to achieve is this. (Collection Area with cards specific part)
Here's my code:
#Composable
fun CollectionCardArea(
collection: Collection,
cardWidth: Dp
) {
Card(
modifier = Modifier
.width(cardWidth)
.padding(start = 2.dp, end = 25.dp, bottom = 5.dp, top = 0.dp)
.clickable { },
shape = RoundedCornerShape(6),
elevation = 4.dp
) {
Box(modifier = Modifier
.fillMaxSize(),
) {
Image(
painter = rememberImagePainter(
data = collection.image
),
contentDescription = collection.name,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
)
}
Box(modifier = Modifier
.fillMaxWidth()
.height(20.dp)
.background(Color.Transparent)
) {
Text(text = collection.name, color = Color.White)
}
}
}
The problem is I cannot find a way to align the text to the bottom of the screen.
Also, I cannot figure out how to blur the text area.
First thing, Card can't position its own child (when there are multiple children). So you need to use something like Column, Row, Box, or else inside the card itself. So instead having a tree like this
- Card
- Box
- Image
- Box
- Text
You can try it like this
- Card
- Box
- Box
- Image
- Box
- Text
Second, as for the blur in Jetpack-Compose you can refer to this answer. But the API itself is only available on Android 12 and up.
Is it possible to add this small grey round-rectangular view on the top of collapsed bottomsheet (see the screenshot) in BottomSheetScaffold (Jetpack Compose)? Does it have the property for it?
To my knowledge there is no property for it in Compose 1.1, but it can be added to the sheetContent:
Column(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier
.background(
color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f),
shape = RoundedCornerShape(50),
)
.size(width = 36.dp, height = 4.dp),
)
}
(Credits to #uragiristereo for the initial suggestion, but .clip(RoundedCornerShape(radius = 50)) didn't seem to work in Compose 1.1. Maybe the behavior changed since the older versions.)
I don't think there is an option or a property for that, but you can easily make it in sheetContent when you are creating your own Bottom Sheet Content.
You can create it easily inside your sheetContent, here is mine for example:
Box(
modifier = Modifier
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.2f))
.size(width = 48.dp, height = 4.dp)
.clip(RoundedCornerShape(radius = 50))
)
How to create BottomNavigation with one of the item is larger than the parent, but without using floatingActionButton. For example like this:
I tried to do that by wrapping the icon with Box but it get cut like this:
Then i try to separate that one button and use constraintLayout to position it, but the constraintLayout cover the screen like this. Even when i color it using Color.Transparent, it always feels like Color.White (i dont know why Color.Transparent never work for me). In this picture i give it Red color for clarity reason.
So how to do this kind of bottomNavBar without having to create heavy-custom-composable?
Update: so i try to make the code based on MARSK and Dharman comment (thanks btw). This is what i
BoxWithConstraints(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(Color.Transparent)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(Color.White)
.align(Alignment.BottomCenter)
)
Row(
modifier = Modifier
.zIndex(56.dp.value)
.fillMaxWidth()
.selectableGroup(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
items.forEach { item ->
val selected = item == currentSection
BottomNavigationItem(
modifier = Modifier
.align(Alignment.Bottom)
.then(
Modifier.height(
if (item == HomeSection.SCAN) 84.dp else 56.dp
)
),
selected = selected,
icon = {
if (item == HomeSection.SCAN) {
ScanButton(navController = navController, visible = true)
} else {
ImageBottomBar(
icon = if (selected) item.iconOnSelected else item.icon,
description = stringResource(id = item.title)
)
}
},
label = {
Text(
text = stringResource(item.title),
color = if (selected) Color(0xFF361DC0) else LocalContentColor.current.copy(
alpha = LocalContentAlpha.current
),
style = TextStyle(
fontFamily = RavierFont,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 18.sp,
),
maxLines = 1,
)
},
onClick = {
if (item.route != currentRoute && item != HomeSection.SCAN) {
navController.navigate(item.route) {
launchSingleTop = true
restoreState = true
popUpTo(findStartDestination(navController.graph).id) {
saveState = true
}
}
}
}
)
}
}
}
It works in preview, but doesn't work when i try in app.
This one in the preview, the transparent working as expected:
And this is when i try to launch it, the transparent doesnt work:
Note: I assign that to bottomBar of Scaffold so i could access the navigation component. Is it the cause that Transparent Color doesnt work?
Update 2: so the inner paddingValues that makes the transparent doesnt work. I fixed it by set the padding bottom manually:
PaddingValues(
start = paddingValues.calculateStartPadding(
layoutDirection = LayoutDirection.Ltr
),
end = paddingValues.calculateEndPadding(
layoutDirection = LayoutDirection.Ltr
),
top = paddingValues.calculateTopPadding(),
bottom = SPACE_X7,
)
Custom Composable are not heavy, really.
Anyway, try this:-
Create a Container of MaxWidth (maybe a BoxWithConstraints or something), keep its background transparent, set the height to wrap content. Create the tabs as usual, but keeping the bigger tab's icon size bigger explicitly using Modifier.size(Bigger Size).
After you have this setup, add another container inside this container with white background, covering a specific height of the original container. Let's say 60%
Now set the z-index of all the icons and tabs to higher than the z-index of this lastly added container. Use Modifier.zIndex for this. And viola, you have your Composable ready.
In order to set a specific percentage height of the inner container, you will need access to the height of the original container. Use BoxWithConstraints for that, or just implement a simple custom Layout Composable