Android Compose Task App Implementation Series: Bottom Bar with a FAB
Production App
Anothertask Version 1.0.0 is available here if you are interested 😉
Objective
Recreate the Bottom Bar with a Floating Action Button (FAB) from the Taskose UI design kit I acquired, utilizing Android Jetpack Compose.
Environment
- Android Studio Iguana | 2023.2.1
- Compose version:
composeBom = "2023.08.00"
- Pixel 6 API 30 Emulator
- Project Minimum SDK: 27
Implementation
Step 1
Download the needed icons here, and add them to res/drawable
directory.
Step 2
Create BottomBarItem.kt
file with the following content.
// Your package ...
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
// import YOUR_PACKAGE_PATH.R
data class BottomBarItemData(
val drawableId: Int = 0,
val selected: Boolean = false,
val onClick: () -> Unit = {}
)
@Composable
fun BottomBarItem(
itemData: BottomBarItemData,
selectedColor: Color = Color(0xFF7980FF),
nonSelectedColor: Color = Color(0xFF464D61).copy(alpha = 0.7f),
iconSize: Dp = 24.dp
) {
IconButton(onClick = { itemData.onClick() }) {
Icon(
imageVector = ImageVector.vectorResource(id = itemData.drawableId),
contentDescription = null,
tint = if (itemData.selected) selectedColor else nonSelectedColor,
modifier = Modifier.size(iconSize)
)
}
}
@Preview
@Composable
fun BottomBarItemPreview() {
val mockOnClick = {}
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.background(Color(0xFF000000))
) {
Text("Selected", color = Color.White)
Spacer(modifier = Modifier.height(8.dp))
BottomBarItem(
itemData = BottomBarItemData(
onClick = mockOnClick,
drawableId = R.drawable.ic_dashboard,
selected = true
),
)
Spacer(modifier = Modifier.height(16.dp))
Text("Not Selected", color = Color.White)
Spacer(modifier = Modifier.height(8.dp))
BottomBarItem(
itemData = BottomBarItemData(
onClick = mockOnClick,
drawableId = R.drawable.ic_profile,
selected = false
),
)
}
}
Step 3
Create BottomBar.kt
file with the following content.
// Your package ...
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LargeFloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
// import YOUR_PACKAGE_PATH.R
// import YOUR_PACKAGE_PATH.BottomBarItem
// import YOUR_PACKAGE_PATH.BottomBarItemData
@Composable
fun BottomBar(
barHeight: Dp = 60.dp,
fabColor: Color = Color(0xFF7980FF),
fabSize: Dp = 64.dp,
fabIconSize: Dp = 32.dp,
cardTopCornerSize: Dp = 24.dp,
cardElevation: Dp = 8.dp,
fabDrawableId: Int = R.drawable.ic_add,
buttons: List<BottomBarItemData> = listOf(
BottomBarItemData(drawableId = R.drawable.ic_dashboard),
BottomBarItemData(drawableId = R.drawable.ic_tasks),
BottomBarItemData(drawableId = R.drawable.ic_chat),
BottomBarItemData(drawableId = R.drawable.ic_profile)
),
fabOnClick: () -> Unit = {}
) {
require(buttons.size == 4) { "BottomBar must have exactly 4 buttons" }
Box(
modifier = Modifier
.fillMaxWidth()
.height(barHeight + fabSize / 2)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(barHeight)
.align(Alignment.BottomCenter),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = cardElevation),
shape = RoundedCornerShape(
topStart = cardTopCornerSize,
topEnd = cardTopCornerSize,
bottomEnd = 0.dp,
bottomStart = 0.dp
)
) {
Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
)
{
BottomBarItem(buttons[0])
BottomBarItem(buttons[1])
Spacer(modifier = Modifier.size(fabSize))
BottomBarItem(buttons[2])
BottomBarItem(buttons[3])
}
}
LargeFloatingActionButton(
modifier = Modifier
.size(fabSize)
.align(Alignment.TopCenter),
onClick = { fabOnClick() },
shape = CircleShape,
containerColor = fabColor,
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation(defaultElevation = 0.dp)
) {
Icon(
imageVector = ImageVector.vectorResource(id = fabDrawableId),
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(fabIconSize)
)
}
}
}
@Preview
@Composable
fun BottomBarPreview() {
val context = LocalContext.current
val items = listOf(
R.drawable.ic_dashboard,
R.drawable.ic_tasks,
R.drawable.ic_chat,
R.drawable.ic_profile
)
// Use a single state to track the currently selected item's index
val selectedIndex = remember { mutableIntStateOf(0) }
// Dynamically generate the buttons based on the items list
val buttons = items.mapIndexed { index, drawableId ->
BottomBarItemData(
drawableId = drawableId,
selected = index == selectedIndex.intValue,
onClick = { selectedIndex.intValue = index } // Update the selected index on click
)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter
) {
BottomBar(buttons = buttons, fabOnClick = {
Toast.makeText(context, "FAB clicked", Toast.LENGTH_SHORT).show()
})
}
}
Preview Result
Explore More
For developers eager to deepen their understanding or explore more about modern Android development, a wealth of resources and guides await.
If you’ve found this resource helpful, consider sharing your support through claps or a follow, and stay tuned for more insights into the evolving landscape of Android development. See you the next time!
Deuk Services: Your Gateway to Leading Android Innovation
Are you looking to boost your business with top-tier Android solutions?Partner with Deuk services and take your projects to unparalleled heights.
🚀 Boost Your Productivity with Notion
New to Notion? Discover how it can revolutionize your productivity
Ready to take your productivity to the next level? Integrate this content into your Notion workspace with ease:
1 Access the Notion Version of this Content
2 Look for the Duplicate
button at the top-right corner of the page
3 Click on it to add this valuable resource to your Notion workspace
Seamlessly integrate this guide into your Notion workspace for easy access and swift reference. Leverage Notion AI to search and extract crucial insights, enhancing your productivity. Start curating your knowledge hub with Notion AI today and maximize every learning moment.