Reyjohn
Reyjohn

Reputation: 2853

How to customize Tabs in Jetpack Compose

I want to customize the look of the tabs in jetpack compose, here is my tabs look like right now:

Current Tab

But I want to look my tabs like this:

Expected Tabs

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?

Upvotes: 10

Views: 15398

Answers (3)

Thracian
Thracian

Reputation: 67443

@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.

enter image description here

If you wish to have a Tabs with shadow and color blending or fluid color change as in this gif

enter image description here

refer this answer

https://stackoverflow.com/a/77333934/5457853

Upvotes: 33

Mattia Ferigutti
Mattia Ferigutti

Reputation: 3738

Animated Tabs

Do you want to create something like this?

enter image description here

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")
)

Upvotes: 3

rimusolamus
rimusolamus

Reputation: 21

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.

Upvotes: 2

Related Questions