Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

TabBar

TabBar is a tab bar: a strip of buttons (tab strip) + a horizontally scrollable content panel with per-page snap (via PageScrollEngine). Tapping a tab scrolls the content to the corresponding page; dragging the content switches the active tab. An animated indicator follows the active tab.

The aesthetics (indicator, tabs at the top or bottom, colors) come from the active Skin (TabBarStyle). Each tab is described by a TabBar.Tab (text, icon or both).

🟡 · Package: dev.crossfly.ui · Extends: Component · Style: TabBarStyle (via getSkin().tabBar())

When to use

  • Navigation between 2–5 sections of a screen (Home / Search / Profile).
  • Paginated content with a swipe gesture between pages + synchronized tabs.

When NOT to use

  • Many sections that don’t fit (the strip scrolls, but becomes less legible) → consider a different navigation.

Appearance

Equal-width tab strip (1/n); if they don’t fit, the strip becomes horizontally scrollable and the tabs switch to automatic width. The indicator slides under (or over, if tabsAtBottom) the active tab. The content panel shows one page at a time with snap.

Complete example (copy, paste and run)

package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      // One content view per tab (the count MUST match the number of tabs).
      Label resumo    = new Label("Conteúdo do Resumo");
      Label detalhes  = new Label("Conteúdo dos Detalhes");
      Label historico = new Label("Conteúdo do Histórico");

      TabBar tabs = new TabBar(
         new TabBar.Tab("Resumo"),
         new TabBar.Tab("Detalhes"),
         new TabBar.Tab("Histórico"));
      tabs.setContent(resumo, detalhes, historico);
      tabs.onTabChanged(i -> System.out.println("aba " + i));

      add(LAY_FILL, tabs);   // the TabBar absorbs the remaining height
   }
}

The snippets below show only the core (what goes inside onShow()). To run them, place them in the MainWindow skeleton above.

Basic usage

TabBar tabs = new TabBar(
   new TabBar.Tab("Home",   new MaterialIcon("")),
   new TabBar.Tab("Buscar", new MaterialIcon("")),
   new TabBar.Tab("Perfil", new MaterialIcon("")));
tabs.setContent(homeView, searchView, profileView);
tabs.onTabChanged(i -> System.out.println("aba " + i));
add(LAY_FILL, tabs);

Constructors

Constructor Description
TabBar(Tab... tabs) Creates the bar with the tabs (activeIndex = 0)

TabBar.Tab (tab descriptor)

Constructor Result
Tab(String text) Text only (TextPos.NONE)
Tab(Icon icon) Icon only (TextPos.NONE)
Tab(String text, Icon icon) Text + icon (TextPos.TEXT_AT_BOTTOM)
Tab(String text, Icon icon, TextPos textPos) Text + icon with a custom position

Properties and customization

Property / method Type What it does
setContent(Component...) TabBar Content per tab (the count must match the tabs, otherwise IllegalArgumentException)
setActiveTab(int) TabBar Activates a tab programmatically (with animation/scroll); no-op if invalid or already active
getActiveIndex() int Index of the active tab
getTabs() Tab[] The tab descriptors
getStrip() / getTabButtons() / getContentPanel() — Access to internals
getIndicatorX() / getIndicatorW() float Current position/width of the indicator (for painting in Design)

Callbacks and events

Callback When it fires
onTabChanged(TabChangedListener) On tab change, by tap, swipe or setActiveTab(...)

TabChangedListener is functional: void onTabChanged(int index).

States

  • Active — only one tab at a time; the indicator animates to it (duration TabBarStyle.switchDuration).
  • User swipe — the indicator interpolates continuously between neighboring tabs; when crossing to another page, activeIndex switches and onTabChanged fires.
  • Programmatic — setActiveTab/tap lock the synchronization until the scroll animation reaches the target.

Text-only tabs

TabBar tabs = new TabBar(
   new TabBar.Tab("Resumo"),
   new TabBar.Tab("Detalhes"),
   new TabBar.Tab("Histórico"));
tabs.setContent(resumoView, detalhesView, historicoView);
add(LAY_FILL, tabs);

Programmatic tab switch

TabBar tabs = new TabBar(new TabBar.Tab("A"), new TabBar.Tab("B"), new TabBar.Tab("C"));
tabs.setContent(viewA, viewB, viewC);
add(LAY_FILL, tabs);
// ... later:
tabs.setActiveTab(2);  // animates to tab C

Best practices

  • The count of setContent(...) must match the number of tabs.
  • For tabs at the bottom (iOS style), use a Skin whose TabBarStyle.tabsAtBottom = true — the layout and the indicator adjust themselves.
  • Don’t force a fixed width on the tabs: if they don’t fit, the strip scrolls on its own.

Common mistakes

  • setContent with the wrong count → IllegalArgumentException. Pass exactly one view per tab.
  • Expecting setActiveTab to fire onTabChanged for the already-active index — it is a no-op when index == activeIndex.

See also

Was this page helpful?