Whenever I navigate within my screen the LazyColumn scrolls perfectly but the top app bar doesn't move at all. Is it possible to tell the enlarged top app bar to scroll or can this only be done with the 4 default top app bars provided in Material 3?
#Composable
fun MyScreen(navController: NavController) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
val mConfiguration = LocalConfiguration.current
val mScreenHeight = mConfiguration.screenHeightDp.dp
val mSize = mScreenHeight / 2
Column {
Box(
modifier = Modifier
.height(mSize)
.weight(1f)
.fillMaxWidth()
) {
LargeTopAppBar(
title = {
Text(
text = "Android Studio Dolphin", overflow = TextOverflow.Visible, maxLines = 1
)
},
scrollBehavior = scrollBehavior)
}
Box(
modifier = Modifier
.height(mSize)
.weight(1f)
.fillMaxWidth()
.background(Color.Green)
) {
MyScreenContent()
// contentPadding ->
// MyScreenContent(contentPadding = contentPadding)
}
}
}
#Composable
fun MyScreenContent(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues()
) {
Box(modifier = modifier.fillMaxSize()) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
contentPadding = contentPadding,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
) {
item {
Text(text = "text", style = TextStyle(fontSize = 18.sp))
}
items(75) {
ListItem(it)
}
}
}
}
You should use a Scaffold applying the nestedScroll modifier.
Something like:
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
LargeTopAppBar(
title = {
Text(
text = "Android Studio Dolphin", overflow = TextOverflow.Visible, maxLines = 1
)
},
scrollBehavior = scrollBehavior)
},
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding,){
//....
}
}
)
Related
What I want
Top fixed item with status bar padding and adaptive radius
Bottom fixed item with navigation bar padding
Adaptive center item, have enough room => Not scrollable, if not => scrollable
Current status
For full demo click this link
Only problem here is when there is no extra space, and we are dragging the scrollable list.
I think it's a bug, because everything is fine except the scrollable column.
window.height - sheetContent.height >= statusBarHeight
Everything is fine.
window.height - sheetContent.height < statusBarHeight
Dragging top fixed item or bottom fixed item, scroll still acting well.
Dragging the scrollable list, sheet pops back to top when the sheetState.offset is approaching statusBarHeight
Test youself
You can test it with these 3 functions, for me, I'm using Pixel 2 Emulator, itemCount at 18,19 could tell the difference.
#Composable
fun CEModalBottomSheet(
sheetState: ModalBottomSheetState,
onCloseDialogClicked: () -> Unit,
title: String = "BottomSheet Title",
toolbarElevation: Dp = 0.dp,
sheetContent: #Composable ColumnScope.() -> Unit,
) {
val density = LocalDensity.current
val sheetOffset = sheetState.offset
val statusBar = WindowInsets.statusBars.asPaddingValues()
val shapeRadius by animateDpAsState(
if (sheetOffset.value < with(density) { statusBar.calculateTopPadding().toPx() }) {
0.dp
} else 12.dp
)
val dynamicStatusBarPadding by remember {
derivedStateOf {
val statusBarHeightPx2 = with(density) { statusBar.calculateTopPadding().toPx() }
val offsetValuePx = sheetOffset.value
if (offsetValuePx >= statusBarHeightPx2) {
0.dp
} else {
with(density) { (statusBarHeightPx2 - offsetValuePx).toDp() }
}
}
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetShape = RoundedCornerShape(topStart = shapeRadius, topEnd = shapeRadius),
content = {},
sheetContent = {
Column(modifier = Modifier.fillMaxWidth()) {
TopTitleItemForDialog(
title = title,
elevation = toolbarElevation,
topPadding = dynamicStatusBarPadding,
onClick = onCloseDialogClicked
)
sheetContent()
}
})
}
#Composable
fun TopTitleItemForDialog(
title: String,
elevation: Dp = 0.dp,
topPadding: Dp = 0.dp,
onClick: () -> Unit
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color.LightGray,
elevation = elevation
) {
Row(
modifier = Modifier.padding(top = topPadding),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.size(16.dp))
Text(
text = title,
maxLines = 1,
modifier = Modifier.weight(1f)
)
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel),
tint = Color.Gray,
modifier = Modifier.size(24.dp)
)
}
}
}
}
class SheetPaddingTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.statusBarColor = android.graphics.Color.TRANSPARENT
window.navigationBarColor = android.graphics.Color.TRANSPARENT
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
Box(
Modifier
.fillMaxSize()
.background(Color.Green), contentAlignment = Alignment.Center
) {
var itemCount by remember { mutableStateOf(20) }
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden) { false }
val scope = rememberCoroutineScope()
Text("显示弹窗", modifier = Modifier.clickable {
scope.launch { state.animateTo(ModalBottomSheetValue.Expanded) }
})
CEModalBottomSheet(sheetState = state,
onCloseDialogClicked = {
scope.launch {
state.hide()
}
}, sheetContent = {
Column(
Modifier
.verticalScroll(rememberScrollState())
.weight(1f, fill = false)
.padding(horizontal = 16.dp),
) {
repeat(itemCount) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(30.dp)
.background(Color.Blue.copy(alpha = 1f - it * 0.04f))
)
}
}
CompositionLocalProvider(
LocalContentColor.provides(Color.White)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.primary)
.padding(vertical = 12.dp)
.navigationBarsPadding(),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(modifier = Modifier.weight(1f),
onClick = {
itemCount = max(itemCount - 1, 0)
}) {
Icon(Icons.Default.KeyboardArrowLeft, "")
}
Text(
modifier = Modifier.weight(1f), text = "$itemCount",
textAlign = TextAlign.Center
)
IconButton(modifier = Modifier.weight(1f),
onClick = {
itemCount++
}) {
Icon(Icons.Default.KeyboardArrowRight, "")
}
}
}
}
)
}
}
}
}
Would you like to help me, i have no idea what i am doing. I want to make a dashboard and there are banner inside it. so i decide to make banner and get data from api to fill it in banner. but i want to make it scroll automaticly
Dashboard screen
#OptIn(ExperimentalPagerApi::class, FlowPreview::class)
#Composable
fun DashboardScreen(
navController: NavHostController = rememberAnimatedNavController(),
datastore: DataStoreRepository,
dashboardViewModel: DashboardViewModel = hiltViewModel(),
) {
val state by dashboardViewModel.dashboardState.collectAsState()
val username = datastore.getUsername().collectAsState(initial = "")
val company = datastore.getUserCompany().collectAsState(initial = "")
val pagerState = rememberPagerState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp, vertical = 20.dp)
.padding(12.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
TopSectionDashboard(name = username.value, company = company.value)
Spacer(modifier = Modifier.height(20.dp))
Box {
state.dashboard?.let {
BannerSlider(
state = pagerState,
listBanner = it.banner
)
Indicators(
size = it.banner.size,
index = pagerState.currentPage,
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
if (state.isLoading) {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator()
}
}
}
}
BannerSlider
#OptIn(ExperimentalPagerApi::class)
#Composable
fun BannerSlider(
modifier: Modifier = Modifier,
state: PagerState,
listBanner: List<Banner>,
) {
HorizontalPager(
count = listBanner.size,
state = state
) {
BannerItem(
item = Banner(
count = listBanner[it].count,
text = listBanner[it].text,
icon = listBanner[it].icon
),
)
}
}
Banner Item
#Composable
fun BannerItem(item: Banner, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.height(200.dp)
) {
Image(
painter = painterResource(id = R.drawable.background_dashboard_slider1),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.align(Alignment.CenterStart)
.offset(y = (-20).dp)
) {
Column(horizontalAlignment = Alignment.Start) {
Text(
text = item.text,
fontFamily = Poppins,
fontSize = 20.sp,
color = Color.White,
modifier = Modifier.padding(horizontal = 24.dp)
)
Text(
text = "${item.count}",
fontFamily = Poppins,
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier
.padding(horizontal = 24.dp)
.offset(y = (-10).dp)
)
}
AsyncImage(
model = item.icon,
contentDescription = "$item.title",
modifier = Modifier.size(120.dp),
alignment = Alignment.CenterEnd
)
}
}
}
Please help me, i have no idea. i really appreciate it to someone who give a hand to me
in a simple way, You can make it possible by
LaunchedEffect(pagerState.currentPage) {
launch {
delay(2000L)
with(pagerState) {
val nextPage = if (currentPage < pageCount - 1) currentPage + 1 else 0
animateScrollToPage(nextPage)
}
}
}
Keep it in my mind to pass count to HorizontalPager as well
Hi I am using Jetpack Compose to create a Heterogenous list. I was successful in implementing it. My requirement is when I try to click an Item in the list, I need to recompose the list. The app crashes when tried to refresh the list with below exception:
java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container
My code is below:
#Composable fun PrepareOverViewScreen(overViewList: List<OverViewListItem>) {
Scaffold(topBar = { TopBar("OverView") },
content = { DisplayOverViewScreen(overViewList = overViewList) },
backgroundColor = Color(0xFFf2f2f2)
)
}
#Composable fun DisplayOverViewScreen(
modifier: Modifier = Modifier, overViewList: List<OverViewListItem>
) {
LazyColumn(modifier = modifier) {
items(overViewList) { data ->
when (data) {
is OverViewHeaderItem -> {
HeaderItem(data,overViewList)
}
}
}
}
}
HeaderItem Composable Function is below :
#Composable fun HeaderItem(overViewHeaderItem: OverViewHeaderItem,overViewList: List<OverViewListItem>) { <br>
var angle by remember {
mutableStateOf(0f)
}
var canDisplayChild by remember {
mutableStateOf(false)
}
**if(canDisplayChild){
HandleHistoryTodayChild(canDisplayChild = true,overViewList)
}**
when (overViewHeaderItem.listType) {
ItemType.IN_PROGRESS_HEADER -> {
Column(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffdc8633)),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start
) {
Text(
text = "In Progress", color = Color.White,
modifier = Modifier.padding(start = 16.dp)
)
}
}
ItemType.HISTORY_TODAY_HEADER -> {
Column(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffd7d7d7)),
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start
) {
Text(
text = stringResource(R.string.history_label), color = Color.Black,
modifier = Modifier.padding(start = 16.dp)
)
}
}
else -> {
Row(modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(color = Color(0xffd7d7d7)),
horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.history_yesterday), color = Color.Black,
modifier = Modifier.padding(start = 16.dp)
)
Spacer(Modifier.weight(1f))
**Image(
painter = painterResource(id = R.drawable.ic_expand_more),
modifier = Modifier
.padding(end = 5.dp)
.rotate(angle)
.clickable {
angle = (angle + 180) % 360f
canDisplayChild = !canDisplayChild
},
contentDescription = "Expandable Image"
)**
}
}
}
}
Handle History info where recomposition is called
#Composable
fun HandleHistoryTodayChild(canDisplayChild:Boolean,overViewList: List<OverViewListItem>) {
if(canDisplayChild){
**PrepareOverViewScreen(overViewList = overViewList)**
}
}
Your problem should be:
Spacer(Modifier.weight(1f))
Try to specify a fixed height and work your solution from there.
I made an expandable floating action button like
here
i want to make it expand verticly and reveal to more buttons, when its note docked to bottom navigation bar, every thing is ok but when i make it docked it's going through bottom nav bar,
When not expanded looking like this
but when expanded like this
How can i make it expand properly
and i also want to align all 3 circle buttons horizontaly.
Thank you so much!
Code:
#Composable
fun ExpandableFAB(
modifier: Modifier = Modifier,
onFabItemClicked: (fabItem: ExpandableFABMenuItem) -> Unit,
menuItems: List<ExpandableFABMenuItem> = ExpandableFABMenuItem.expandableFabMenuItems
) {
var expandedState by remember { mutableStateOf(false) }
val rotationState by animateFloatAsState(
targetValue = if (expandedState) 45f else 0f
)
Column(
modifier = modifier.wrapContentSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(
visible = expandedState,
enter = fadeIn() + expandVertically(),
exit = fadeOut()
) {
LazyColumn(
modifier = Modifier.wrapContentSize(),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
items(menuItems.size) { index ->
ExpandableFABItem(
item = menuItems[index],
onFabItemClicked = onFabItemClicked
)
}
item {}
}
}
FloatingActionButton(
onClick = { expandedState = !expandedState },
backgroundColor = Blue
) {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = "FAB",
modifier = Modifier.rotate(rotationState),
)
}
}
}
#Composable
fun ExpandableFABItem(
item: ExpandableFABMenuItem,
onFabItemClicked: (item: ExpandableFABMenuItem) -> Unit
) {
Row(
modifier = Modifier
.wrapContentSize()
.padding(end = 10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = item.label,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.background(MaterialTheme.colors.surface)
.padding(horizontal = 6.dp, vertical = 4.dp)
)
Box(
modifier = Modifier
.clip(CircleShape)
.size(42.dp)
.clickable { onFabItemClicked }
.background(Blue),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = item.icon,
contentDescription = "Create New Note",
tint = White
)
}
}
}
I want to customize the look of the tabs in jetpack compose, here is my tabs look like right now
But I want to look my tabs like this:
I am creating tabs like this way
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = MaterialTheme.colors.primary,
contentColor = Color.White
) {
filters.forEachIndexed { index, filter ->
Tab(
text = {
Text(
text = filter.name.replaceFirstChar {
if (it.isLowerCase()) {
it.titlecase(Locale.getDefault())
} else {
it.toString()
}
}
)
},
selected = pagerState.currentPage == index,
onClick = { scope.launch { pagerState.animateScrollToPage(index) } },
)
}
}
How can I achieve that look, I have searched a lot but didn't find any clue, can anyone help?
#Composable
fun CustomTabs() {
var selectedIndex by remember { mutableStateOf(0) }
val list = listOf("Active", "Completed")
TabRow(selectedTabIndex = selectedIndex,
backgroundColor = Color(0xff1E76DA),
modifier = Modifier
.padding(vertical = 4.dp, horizontal = 8.dp)
.clip(RoundedCornerShape(50))
.padding(1.dp),
indicator = { tabPositions: List<TabPosition> ->
Box {}
}
) {
list.forEachIndexed { index, text ->
val selected = selectedIndex == index
Tab(
modifier = if (selected) Modifier
.clip(RoundedCornerShape(50))
.background(
Color.White
)
else Modifier
.clip(RoundedCornerShape(50))
.background(
Color(
0xff1E76DA
)
),
selected = selected,
onClick = { selectedIndex = index },
text = { Text(text = text, color = Color(0xff6FAAEE)) }
)
}
}
}
Result is as in gif.
In addition to Thracian's answer:
If you need to keep the state of tabs after the screen orientation change, use rememberSaveable instead of remember for selectedIndex.
Animated Tabs
Do you want to create something like this?
I tried to use the Material Design 3 library but it makes everything much more difficult, so I created the TabRow from scratch.
You can use this code to save you some time:
#Composable
fun AnimatedTab(
items: List<String>,
modifier: Modifier,
indicatorPadding: Dp = 4.dp,
selectedItemIndex: Int = 0,
onSelectedTab: (index: Int) -> Unit
) {
var tabWidth by remember { mutableStateOf(0.dp) }
val indicatorOffset: Dp by animateDpAsState(
if (selectedItemIndex == 0) {
tabWidth * (selectedItemIndex / items.size.toFloat())
} else {
tabWidth * (selectedItemIndex / items.size.toFloat()) - indicatorPadding
}
)
Box(
modifier = modifier
.onGloballyPositioned { coordinates ->
tabWidth = coordinates.size.width.toDp
}
.background(color = gray100, shape = Shapes.card4)
) {
MyTabIndicator(
modifier = Modifier
.padding(indicatorPadding)
.fillMaxHeight()
.width(tabWidth / items.size - indicatorPadding),
indicatorOffset = indicatorOffset
)
Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly
) {
items.forEachIndexed { index, title ->
MyTabItem(
modifier = Modifier
.fillMaxHeight()
.width(tabWidth / items.size),
onClick = {
onSelectedTab(index)
},
title = title
)
}
}
}
}
#Composable
private fun MyTabIndicator(
modifier: Modifier,
indicatorOffset: Dp,
) {
Box(
modifier = modifier
.offset(x = indicatorOffset)
.clip(Shapes.card4)
.background(white100)
)
}
#Composable
private fun MyTabItem(
modifier: Modifier,
onClick: () -> Unit,
title: String
) {
Box(
modifier = modifier
.clip(Shapes.card4)
.clickable(
interactionSource = MutableInteractionSource(),
indication = null
) { onClick() },
contentAlignment = Alignment.Center
) {
Text(text = title)
}
}
Usage:
var selectedTab by remember { mutableStateOf(0) }
AnimatedTab(
modifier = Modifier
.height(40.dp)
.fillMaxSize(.9f),
selectedItemIndex = selectedTab,
onSelectedTab = { selectedTab = it },
items = listOf("first", "second")
)