I am trying to develop a custom Topbar like below:
I have tried developing TopAppBar using jetpack compose but now wanted to have collapsed and expanded effect on Topbar and because of that, I wanted to develop a custom top bar. Please let me know if the above screenshot can be achieved from the below code.
Apologies as I am new to jetpack.
#Composable
fun TopNavBar(
navController: NavController,
title: String,
showActions: Boolean = false,
showAvatar: Boolean = true
) {
val toolbarHeight = 52.dp
val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }
TopAppBar(
modifier = Modifier
.height(toolbarHeight)
.offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) },
// title = { Text("toolbar offset is ${toolbarOffsetHeightPx.value}") }
backgroundColor = MaterialTheme.colors.background,
title = {
Text(
text = title,
color = MaterialTheme.colors.primaryLabel,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.body1
)
},
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
painter = painterResource(id = R.drawable.ic_back),
contentDescription = stringResource(id = R.string.back),
tint = Grey8
)
}
},
actions = {
// RowScope here, so these icons will be placed horizontally
if (showActions) {
IconButton(onClick = { }) {
Icon(
painter = painterResource(id = R.drawable.ic_menu_search),
contentDescription = stringResource(id = R.string.search),
tint = Grey8
)
}
IconButton(onClick = { }) {
Icon(
painter = painterResource(id = R.drawable.ic_create),
contentDescription = stringResource(id = R.string.create),
tint = Grey8
)
}
}
if (showAvatar) {
IconButton(onClick = { }) {
Image(
painter = painterResource(id = R.drawable.ic_avatar),
contentDescription = stringResource(id = R.string.profile),
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
)
}
}
}
)
Related
I have a composable of snackbar. Below is the code
#Composable
fun CealSnackbar(title: String,performAction: () -> Unit,) {
val counterState = remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
while(true) {
delay(4000)
counterState.value = false
performAction()
}
}
AnimatedVisibility(
visible = counterState.value,
enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut(),
) {
Card(
modifier = Modifier
.absoluteOffset(x = 0.dp, y = 10.dp)
.padding(horizontal = 20.dp)
.fillMaxWidth()
.statusBarsPadding()
.background(
color = MaterialTheme.colors.primaryVariant,
shape = RoundedCornerShape(10.dp)
), elevation = 20.dp
) {
Row(
modifier = Modifier
.background(color = MaterialTheme.colors.primaryVariant)
.padding(12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.error_circle),
contentDescription = title
)
Text(
text = title,
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
}
I want the animation when the snackbar appears and disappears and it should be usable in any component
So while using this in other composable I am trying to display it when there is an error msg, the error msg is a state flow
if(errorMsg.isNotEmpty()){
CealSnackbar(
title = errorMsg, performAction = {
registrationViewModel.setErrorMsg("")
}
)
}
If I use it like above the animation does not take place, how should I refactor it so that I use it any composable when there is an error msg
Use SnackbarHost
val snackState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
SnackbarHost(
hostState = snackState
){
CealSnackbar(
title = errorMsg, performAction = {
registrationViewModel.setErrorMsg("")
}
)
}
I am using the Material 3 Scaffold with the material 3 TopAppBar. I need to change the height of TopAppBar but cannot use MediumTopAppBar as it's too heightened. Is there any way to do the same?
Here is the code
val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
text = "About",
modifier = Modifier
.padding(start = 30.dp, bottom = 12.dp)
.wrapContentSize(),
color = colorResource(id = R.color.black)
)
},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = Color.Red,
titleContentColor = Color.Black,
),
modifier = Modifier.fillMaxWidth(),
navigationIcon = {
Button(onClick = { }) {
Text(text = "B1")
}
}, actions = {
Button(onClick = {}) {
Text(text = "B2")
}
},
scrollBehavior = scrollBehaviour
)
},
content = {
}
)
Imagine I have a TopAppBar (1) like
In Code similar to:
TopAppBar(
backGroundColor = Colors.black
) {
Row(modifier = Modifier) {
Icon( // 2
modifier = Modifier.size(24.dp),
id = R.drawable.ic_hamburger_menu,
onClick = {
openMenu()
}
)
Text( // 3
modifier = Modifier,
text = "Page Title"
)
Icon( // 4.1
modifier = Modifier.size(24.dp),
id = R.drawable.ic_share,
onClick = {
//..
}
)
Icon( // 4.2
modifier = Modifier.size(24.dp),
id = R.drawable.ic_magnifing_glass,
onClick = {
openTopAppBarWithSearchContent()
}
)
Icon( // 5
modifier = Modifier.size(24.dp),
id = R.drawable.ic_ellipsis,
onClick = {
//..
}
)
}
}
When cliking on the magnifier glass (4.2), I would like to replace the complete (1) content (Manu icon, Text, Share icon, glass icon, points icon) of the top app bar with an individual Composable; let's say a search/input field..
With other words: openTopAppBarWithSearchContent() should replace its parent TopAppBars content.
How can this be realized in a Jetpack Compose way?
You can use a mutableState to change your layout based on state value. When you change state value it will trigger a recomposition and based on current value recomposition will pick desired layout.
#Composable
fun MyTopAppBar(
backGroundColor: Color = MaterialTheme.colors.primary
) {
var showFirstMenu by remember { mutableStateOf(true) }
if (showFirstMenu) {
Row(
modifier = Modifier
.background(MaterialTheme.colors.primary)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
// 2
IconButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = null, tint = Color.White)
}
Text( // 3
modifier = Modifier,
text = "Page Title",
color = Color.White
)
Spacer(modifier = Modifier.weight(1f))
// 4.1
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = null,
tint = Color.White
)
}
// 4.2
IconButton(onClick = { showFirstMenu = !showFirstMenu }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = null,
tint = Color.White
)
}
// 5
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = null,
tint = Color.White
)
}
}
} else {
TopAppBar(
title = { Text("Another Page") },
navigationIcon = {
IconButton(onClick = {}) {
Icon(Icons.Default.Menu, "Menu")
}
},
actions = {
IconButton(onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Favorite, contentDescription = null)
}
IconButton(onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Refresh, contentDescription = null)
}
IconButton(
onClick = { /* doSomething() */ }) {
Icon(Icons.Filled.Call, contentDescription = null)
}
}
)
}
}
And you can TopAppbar instead of Row to create one.
Can anyone please point me out how to change this white background color of the app? Setting a color or background color on the Surface is having no impact. I've set the cyan background for the content inside the Scaffold just to debug the issue.
class MainActivity : ComponentActivity() {
...
setContent {
ChakkarTheme {
Surface(
color = Color.Red, modifier = Modifier
.fillMaxSize()
.background(Color.DarkGray)
) {
ChakkarApp()
}
...
#Composable
fun ChakkarApp() {
Scaffold(
topBar = { TopAppBar(title = { Text(appTitle) }, navigationIcon = navigationIcon) },
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
if (showFab) {
FloatingActionButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Filled.Add, contentDescription = null)
}
}
},
bottomBar = {
BottomNavigation() {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
bottomNavItems.forEach { screen ->
BottomNavigationItem(
icon = { Icon(imageVector = screen.icon, contentDescription = null) },
label = { Text(stringResource(id = screen.resourceId)) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { paddingValues ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.background(Color.Cyan)
.padding(paddingValues)
.padding(16.dp)
) {
NavHost(
navController = navController,
startDestination = Screen.Running.route
) {
...
Thanks for your help!
The default background color of Scaffold is MaterialTheme.colors.background.
You can specify a custom value:
Scaffold(
...
backgroundColor = Color.DarkGray,
)
Could you try add modifier = Modifier.fillMaxSize().background(color = Color.Black) or backgroundColor = Color.Black in your Scaffold?
+ Edit:
add Modifier is not working. Try to add backgroundColor in your Scaffold.
Like this:
MaterialTheme {
Scaffold(
content = {
Box(modifier = Modifier.size(500.dp).background(color = Color.Cyan))
},
backgroundColor = Color.Black
)
}
From the below code snippet set full background color
Modifier.fillMaxSize() It gives a full height and width.
Modifier.background(Color.Grey) By usging background can set a background color.
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Grey)
) {
Column(
modifier = Modifier
.verticalScroll(
state = rememberScrollState()
)
) {
Text(
text = profileViewModel.loginUserDetail.lastName,
fontSize = 18.sp,
color = Color.White,
fontFamily = myFontFamily,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(5.dp, 5.dp, 5.dp, 5.dp),
)
}
}
I have the following composable function to build a Chip:
#Composable
fun CategoryChip(
category: String,
isSelected: Boolean = false,
onSelectedCategoryChanged: (String) -> Unit,
onExecuteSearch: () -> Unit
) {
Surface(
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
elevation = 8.dp,
shape = RoundedCornerShape(16.dp),
color = when {
isSelected -> colorResource(R.color.teal_200)
else -> colorResource(R.color.purple_500)
}
) {
Row(modifier = Modifier
.toggleable(
value = isSelected,
onValueChange = {
onSelectedCategoryChanged(category)
onExecuteSearch()
}
)) {
Text(
text = category,
style = MaterialTheme.typography.body2,
color = Color.White,
modifier = Modifier.padding(8.dp)
)
}
}
}
This creates the following chip:
But what I am trying to achieve is the following:
Is it possible to create a shape like that with Jetpack Compose?
Starting with M2 1.2.0-alpha02 you can use the Chip or FilterChip composable:
Chip(
onClick = { /* Do something! */ },
border = BorderStroke(
ChipDefaults.OutlinedBorderSize,
Color.Red
),
colors = ChipDefaults.chipColors(
backgroundColor = Color.White,
contentColor = Color.Red
),
leadingIcon = {
Icon(
Icons.Filled.Settings,
contentDescription = "Localized description"
)
}
) {
Text("Change settings")
}
With M3 (androidx.compose.material3) you can use one of these options:
AssistChip
FilterChip
InputChip
SuggestionChip
Something like:
AssistChip(
onClick = { /* Do something! */ },
label = { Text("Assist Chip") },
leadingIcon = {
Icon(
Icons.Filled.Settings,
contentDescription = "Localized description",
Modifier.size(AssistChipDefaults.IconSize)
)
}
)
Yes, all you have to do is add a border to your surface.
Surface(
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
elevation = 8.dp,
shape = RoundedCornerShape(16.dp),
border = BorderStroke(
width = 1.dp,
color = when {
isSelected -> colorResource(R.color.teal_200)
else -> colorResource(R.color.purple_500)
}
)
)
Building on Code Poet's answer i wanted to show how to do a Material Chip with background color:
#Composable
fun buildChip(label: String, icon: ImageVector? = null) {
Box(modifier = Modifier.padding(8.dp)) {
Surface(
elevation = 1.dp,
shape = MaterialTheme.shapes.small,
color = Color.LightGray
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (icon != null) Icon(
icon,
modifier = Modifier
.fillMaxHeight()
.padding(horizontal = 4.dp)
)
Text(
label,
modifier = Modifier.padding(8.dp),
style = MaterialTheme.typography.button.copy(color = Color.DarkGray)
)
}
}
}
}
Simply use ChipDefaults.outlinedBorder and Defaults.outlinedChipColors():
Chip(
onClick = {},
border = ChipDefaults.outlinedBorder,
colors = ChipDefaults.outlinedChipColors(),
) {
Text(
text = "Trends",
)
}
compose version 1.2.1 , kotlinCompilerExtensionVersion 1.3.1
Also add accompanist Flow library
implementation
"com.google.accompanist:accompanist-flowlayout:0.26.4-beta"
We will use SuggestionChip composable function to create chips.
#Composable
fun SuggestionChipLayout() {
val chips by remember { mutableStateOf(listOf("India", "France", "Spain","Netherland","Austarlia","Nepal")) }
var chipState by remember { mutableStateOf("") }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Suggestion Chip Example")
Spacer(modifier = Modifier.height(20.dp))
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
mainAxisSpacing = 16.dp,
crossAxisSpacing = 16.dp,
) {
chips.forEach {
SuggestionChipEachRow(chip = it, it == chipState) { chip ->
chipState = chip
}
}
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun SuggestionChipEachRow(
chip: String,
selected: Boolean,
onChipState: (String) -> Unit
) {
SuggestionChip(onClick = {
if (!selected)
onChipState(chip)
else
onChipState("")
}, label = {
Text(text = chip)
},
border = SuggestionChipDefaults.suggestionChipBorder(
borderWidth = 1.dp,
borderColor = if (selected) Color.Transparent else PurpleGrey40
),
modifier = Modifier.padding(horizontal = 16.dp),
colors = SuggestionChipDefaults.suggestionChipColors(
containerColor = if (selected) Purple80 else Color.Transparent
),
shape = RoundedCornerShape(16.dp)
)
}
Filter Chips: In filter chip we can select multiple items at a time
compose version 1.2.1 , kotlinCompilerExtensionVersion 1.3.1 Also add accompanist Flow library
implementation
"com.google.accompanist:accompanist-flowlayout:0.26.4-beta"
we will use FilterChip composable function to create Filter Chip
#SuppressLint("MutableCollectionMutableState")
#Composable
fun FilterChipLayout() {
val originalChips by remember {
mutableStateOf(
listOf(
"chip1",
"chip2",
"chip3",
"chip4",
"chip5"
)
)
}
val temp: Set<Int> = emptySet()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Filter Chip Example")
Spacer(modifier = Modifier.height(20.dp))
FilterChipEachRow(chipList = originalChips, tempList = temp)
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun FilterChipEachRow(
chipList: List<String>,
tempList: Set<Int>
) {
var multipleChecked by rememberSaveable { mutableStateOf(tempList) }
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
mainAxisSpacing = 16.dp,
crossAxisSpacing = 16.dp,
) {
chipList.forEachIndexed { index, s ->
FilterChip(selected = multipleChecked.contains(index), onClick = {
multipleChecked = if (multipleChecked.contains(index))
multipleChecked.minus(index)
else
multipleChecked.plus(index)
}, label = {
Text(text = s)
},
border = FilterChipDefaults.filterChipBorder(
borderColor = if (!multipleChecked.contains(index)) PurpleGrey40 else Color.Transparent,
borderWidth = if (multipleChecked.contains(index)) 0.dp else 2.dp
),
shape = RoundedCornerShape(8.dp),
leadingIcon = {
(if (multipleChecked.contains(index)) Icons.Default.Check else null)?.let {
Icon(
it,
contentDescription = ""
)
}
}
)
}
}
}