Membuat Custom Tab Menu (Jetpack Compose, Animation)

Bagian 2: Digital Bank Application Exploration

Veronica Putri Anggraini
7 min read2 days ago

Hello Folks! Pada artikel ini saya akan membagikan step-by-step untuk membuat custom tab menu. Pembuatan custom tab menu ini sangat sederhana dan mudah untuk diimplementasikan. Namun artikel ini merupakan kelanjutan dari dua part sebelumnya :

Jika teman — teman belum mempelajari kedua artikel diatas, bisa cek terlebih dahulu part sebelumnya agar lebih mudah memahami bagian ini. Dibawah ini merupakan custom tab menu yang akan dibuat.

Terdapat beberapa komponen yang perlu diperhatikan saat kita membuat custom menu seperti diatas.

  1. Item Tab Menu
  2. Indicator Tab Menu
  3. Custom Menu Componen Secara Utuh

Membuat Item Tab Menu

Item tab menu terdiri komponen text sehingga cukup mudah untuk dibuat. Perhatikan langkah — langkah berikut ini :

  • Sebagai langkah awal untuk membuat tab menu, buat lah sebuah file baru didalam package component dengan nama CustomMenu.kt.
  • Buatlah sebuah private composable function dengan nama CustomTabItem didalam CustomMenu.kt dengan kode dibawah ini.
@Composable
private fun CustomTabItem(
isSelected: Boolean,
onClick: () -> Unit,
tabWidth: Dp,
text: String,
) {
val tabTextColor: Color by animateColorAsState(
targetValue = if (isSelected) {
Black
} else {
Color(0xff898D99)
},
animationSpec = tween(easing = LinearEasing), label = "animation",
)
Text(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
onClick()
}
.width(tabWidth)
.padding(
vertical = 6.dp,
horizontal = 6.dp,
),
text = text,
textAlign = TextAlign.Center,
color = tabTextColor, fontSize = 20.sp
)
}

Kode diatas merupakan composable function untuk item dari custom tab menu. Berdasarkan desain komponen item tab menu berupa text yang memiliki perbedaan warna antara kondisi selected dan unselected. Untuk lebih lengkapnya perhatikan beberapa poin dibawah ini:

  • isSelected merupakan sebuah parameter dari function diatas, yang menjadi flag boolean yang akan menotify item tab dalam kondisi selected atau un-selected. Selain itu flag ini juga digunakan untuk mengubah tampilan tab termasuk warna teks yang berbeda untuk setiap kondisi.
  • onClick: () -> Unit merupakan parameter berupa sebuah fungsi lambda yang akan dieksekusi saat item tab diklik.
  • tabWidth: Dp merupakan parameter berupa lebar yang bisa di-set sesuai kebutuhan dari item tab.
  • text: String merupakan sebuah parameter berupa teks yang nantinya akan ditampilkan di dalam item tab.
  • animateColorAsState merupakan sebuah fungsi compose yang membuat value dari warna yang dieksekusi sebagai animasi. Fungsi ini membuat sebuah transisi dua value warna.
  • targetValue = if (isSelected) { … } else {… } merupakan block code berupa logika untuk menangani perubahan warna. Jika isSelected dalam kondisi true maka targetValue bernilai hitam dan sebaliknya jika dalam kondisi false, maka targetValue akan bernilai abu-abu.
  • animationSpec = tween(easing = LinearEasing) merupakan block code yang mendefinisikan bagaimana behaviour dari animasi. Penggunaan tween akan menciptakan transisi yang smooth. Sedangkan penggunaan LinearEasing akan membuat animasi berjalan dengan kecepatan konstan.

Catatan : tabTextColor menggunakan delegation property yang akan secara otomatis diupdate jika terjadi perubahan value pada targetValue.

  • .clickable(…) merupakan properti yang akan membuat Text dapat bersifat clickable.
  • interactionSource = remember { MutableInteractionSource() } merupakan block code yang akan membuat MutableInteractionSource melacak interaksi berupa action klik pada Text. Sedangkan fungsi remember akan memastikan bahwa MutableInteractionSource yang sama akan digunakan di seluruh proses rekomposisi.
  • indication = null merupakan properti yang akan memastikan compose tidak akan menampilkan response visual / efek visual apa pun, dalam case ini tidak akan muncul frame ataupun highlight saat item tab di klik.

Membuat Indicator Tab Menu

Indicator tab menu pada projek ini menampilkan komponen underline saat dalam kondisi selected. Untuk membuat indicator tab menu perhatikan langkah — langkah dibawah ini :

  • Buatlah sebuah private composable funtion dengan nama CustomTabIndicator pada CustomMenu.kt file
  • Selanjutnya tambahkan kode seperti dibawah ini.
@Composable
private fun CustomTabIndicator(
indicatorOffset: Dp,
indicatorColor: Color,
) {
Box(
modifier = Modifier
.width(70.dp)
.height(3.dp)
.offset(
x = indicatorOffset,
)
.background(
color = indicatorColor,
),
)
}

Kode diatas akan menampilkan indikator underline yang bergerak di bawah tab yang sedang dipilih dalam tab menu. Untuk lebih penjelasan lebih detail perhatikan beberapa poin berikut.

  • indicatorOffset: Dp merupakan parameter yang menentukan posisi indikator dalam kondisi horizontal. Nilai ini akan berubah saat tab berbeda diklik, dan indikator juga akan bergerak sesuai dengan nilai ini.
  • indicatorColor: Color merupakan parameter yang menentukan warna indikator.
  • Box merupakan komponen yang digunakan untuk membuat indikator underline.
  • .width(70.dp) merupakan properti yang mengatur lebar indikator yang akan selalu memiliki lebar 70dp.
  • .height(3.dp) merupakan properti yang akan mengatur tinggi indikator atau ketebalan garis indikator.
  • .offset(x = indicatorOffset) merupakan properti yang membuat indikator bergerak. Karena offset sendiri merupakan komponen yang digunakan untuk menggeser posisi elemen. Sedangkan x = indicatorOffset akan menentukan bahwa pergeseran hanya terjadi pada sumbu horizontal/x.
  • .background(color = indicatorColor) merupakan properti yang akan mengatur warna background dari Box.

Membuat Custom Tab Menu

Setelah membuat Item dan Indicator Tab Menu, selanjutnya buatlah Custom Tab Menu secara utuh. Untuk membuat custom tab menu secara utuh perhatikan langkah — langkah berikut :

  • Buatlah sebuah composable function dengan nama CustomTab pada file CustomMenu.kt file.
  • Tambahkan kode pada composable function CustomTab seperti dibawah ini.
@Composable
fun CustomTab(
selectedItemIndex: Int,
items: List<String>,
modifier: Modifier = Modifier,
tabWidth: Dp = 70.dp,
onClick: (index: Int) -> Unit,
) {
val indicatorOffset: Dp by animateDpAsState(
targetValue = tabWidth * selectedItemIndex,
animationSpec = tween(easing = LinearEasing), label = "animation",
)

Column(
modifier = modifier
.padding(horizontal = 20.dp)
.height(intrinsicSize = IntrinsicSize.Min),
) {
Row(
horizontalArrangement = Arrangement.Center
) {
items.mapIndexed { index, text ->
val isSelected = index == selectedItemIndex
CustomTabItem(
isSelected = isSelected,
onClick = {
onClick(index)
},
tabWidth = tabWidth,
text = text,
)
}
}
CustomTabIndicator(
indicatorOffset = indicatorOffset,
indicatorColor = Color(0xff399918),
)
}
}

Kode diatas akan menampilkan custom tab secara utuh yang merupakan gabungan dari item dan indicator custom tab. Untuk penjelasan lebih detail perhatikan beberapa poin dibawah ini :

  • selectedItemIndex: Int merupakan parameter indeks dari tab menu yang dipilih, nantinya parameter ini akan menentukan tab menu mana yang harus di-highlight dan di mana indikator harus ditempatkan.
  • items: List<String> merupakan parameter daftar teks yang akan ditampilkan di setiap tab. Setiap string dalam daftar akan menjadi label untuk satu tab.
  • tabWidth: Dp = 70.dp merupakan parameter berupa lebar default untuk setiap item tab.
  • onClick: (index: Int) -> Unit merupakan parameter berupa sebuah fungsi lambda yang akan dipanggil saat tab diklik, sehingga value index akan memberi informasi indeks tab mana yang diklik.
  • animateDpAsState merupakan fungsi yang membuat nilai Dp yang akan dieksekusi sebagai animasi untuk membuat pergerakan indikator underline terlihat halus.
  • targetValue = tabWidth * selectedItemIndex merupakan logika untuk menghitung posisi indikator.
  • animationSpec = tween(easing = LinearEasing) merupakan block code yang mendefinisikan bagaimana behaviour dari animasi. Penggunaan tween akan menciptakan transisi yang smooth. Sedangkan penggunaan LinearEasing akan membuat animasi berjalan dengan kecepatan konstan.
  • Column() merupakan komponen yang digunakan untuk menempatkan baris tab dan indikator CustomTabIndicator secara vertikal.
  • Row() merupakan komponen yang menempatkan item-item tab CustomTabItem secara horizontal.
  • items.mapIndexed { index, text -> … } merupakan cara untuk membuat item tab untuk setiap string dalam daftar items, untuk mapIndexed sendiri digunakan untuk mengiterasi daftar items dan mendapatkan indeks dan teks untuk setiap item.

Update Home Screen Code

Pada artikel sebelumnya pemanggilan komponen card dilakukan pada MainActivity class. Pada bagian ini buatlah sebuah composable function yang nantinya akan menampilkan home screen, dengan langkah — langkah sebagai berikut.

  • Buatlah sebuah package home didalam package UI.
  • Selanjutnya buatlah file baru dengan nama HomeScreen.kt
  • Buatlah sebuah composable function dengan nama HomeScreen
  • Pindahkan SavingCard dari MainActivity kedalam composable function HomeScreen.
  • Tambahkan HomeScreen ke dalam MainActivity
  • Panggil CustomTab komponen kedalam HomeScreen composable function. Sehingga seluruh kode pada HomeScreen akan seperti dibawah ini.
@Composable
fun HomeScreen(
context: Context
) {
val textNumber = "9876543210"
val textName = "greenSaving"
val balance = "1.000.000.000"
var isShowMessage by remember { mutableStateOf(false) }
var message by remember { mutableStateOf("") }
val isVisibleFlow = BankingDataStore.getIsVisible(context)
val isVisible by isVisibleFlow.collectAsState(initial = true)
val coroutineScope = rememberCoroutineScope()

Scaffold(
modifier = Modifier
.fillMaxSize()
) { innerPadding ->
Spacer(modifier = Modifier.height(50.dp))
val (selected, setSelected) = remember {
mutableIntStateOf(0)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.padding(innerPadding)
) {
CustomTab(
items = listOf("Akun", "Kartu"),
selectedItemIndex = selected,
onClick = setSelected,
)
Spacer(modifier = Modifier.height(1.dp))
GrayLine(Modifier, Color(0xFFF3F4F6), 10f)
Spacer(modifier = Modifier.height(10.dp))
SavingCard(
modifier = Modifier.padding(horizontal = 30.dp, vertical = 20.dp),
textNumber = textNumber,
textName = textName,
balance = balance,
onClick = {
context.apply {
setClipboard("Account Number", textNumber)
isShowMessage = true
message = "Berhasil di-Copy!"
}
},
isVisible = isVisible,
eyeClick = {
coroutineScope.launch {
BankingDataStore.saveIsVisible(context, !isVisible)
}
})
if (isShowMessage) {
CustomOnTopToast(
message = message,
onDismiss = { isShowMessage = false }
)
}
}
}
}
  • Sedangkan pada MainActivity akan seperti dibawah ini.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
GreenBankApplicationTheme {
HomeScreen(this)
}
}
}
}
  • Selanjutnya jalankan aplikasi, dan akan tampak seperti dibawah ini.

Cukup mudah bukan? :) Jika teman — teman merasa artikel ini bermanfaat, jangan lupa follow akun ini dan clap untuk artikel berikutnya.

Cheers!!

--

--

Veronica Putri Anggraini
Veronica Putri Anggraini

Written by Veronica Putri Anggraini

Software Engineer Android @LINE Bank 🤖 Google Developer Expert for Android https://github.com/veroanggraa

Responses (1)