# Tabs Tray Item Animations
Tab items in the list (or grid) by default have three configured animations via animateItems():
- fadeIn: on enter, the item's alpha animates from 0 to 1, revealing the item
- fadeOut: on exit, the item's alpha animates from 1 to 0, hiding the item
- placement: when an item is placed or moved, it is translated and surrounding items are shuffled
When a new tab group is created (via drag-and-drop onto another tab, or by saving a
multi-selection), the group's item plays a one-shot entrance animation that scales up
with a slight overshoot and fades in, while the surrounding tabs avoid the usual
fade/shuffle so the group appears to "land" in place and distractions are minimized.
This document describes how that animation is driven. See `Bug 2039969`.
See also [https://developer.android.com/develop/ui/views/animations/spring-animation]
## Lifecycle (data flow)
The animation is gated by a single piece of state, `TabsTrayState.TabGroupState.enteringGroupId`.
It is set when a group is created and cleared once the animation finishes, so the
entrance plays exactly once.
```{mermaid}
stateDiagram-v2
state GroupEntranceAnimation {
[*] --> Alpha
[*] --> Scale
state Alpha {
[*] --> ALPHA_START
ALPHA_START : snapTo 0f (invisible)
ALPHA_START --> ALPHA_END
ALPHA_END: springTo 1f (fully visible), StiffnessMedium
ALPHA_END --> [*]
}
state Scale {
[*] --> SCALE_START
SCALE_START : snapTo 0.92f
SCALE_START --> SCALE_PEAK
SCALE_PEAK : springTo 1.06f, StiffnessMediumLow
SCALE_PEAK --> SCALE_END
SCALE_END : springTo 1f, StiffnessMediumLow
SCALE_END --> Notify
Notify : Update enteringGroupId to NULL
Notify --> [*]
}
}
state DefaultAnimation {
newGroupPlaying: Is a group entrance animation playing?
(enteringGroupId non-null)
newGroupPlaying --> groupPlayingYes
newGroupPlaying --> groupPlayingNo
groupPlayingNo: No
groupPlayingYes: Yes
groupPlayingYes --> disableAnimateItems
disableAnimateItems: Disable item animations for fadeIn, fadeOut, placement
disableAnimateItems --> [*]
groupPlayingNo --> enableAnimateItems
enableAnimateItems: Play default item animations for fadeIn, fadeOut, placement
enableAnimateItems --> [*]
}
[*] --> enteringGroupId
enteringGroupId --> enteringGroup
enteringGroup: Is tab item new group entering composition?
(id == enteringGroupId)
enteringGroup --> Yes
enteringGroup --> No
Yes --> GroupEntranceAnimation
No --> DefaultAnimation
```
## Grid vs. list
The two layouts apply the entrance with slight differences:
- **Grid** hoists `tabGroupEntranceAnimation` onto the outer `InteractableDragItemContainer`,
above the `LazyGridItem`, so the `1.06` overshoot is not clipped to the item's bounds.
- **List** combines the entrance scale with the interaction scale inside
`tabItemGroupListInteractionAnimation` (`combinedScale = interactionScale * entranceScale`).
This is because the overshoot displays differently on a list, which has one item per row.
## Suppressing default item animations
While `enteringGroupId != null`, `defaultGridItemAnimation` / `defaultListItemAnimation`
pass `null` for the `fadeOut` and `placement` specs. This prevents two unwanted effects
per the spec:
- "Ghost" tabs of the items being combined fading out.
- Neighboring tabs shuffling to make room; the group should appear at the position it
was dropped.
## Key references
- `SharedTabItemUi.kt` — holds most of the animation logic.
- `InteractableGrid.kt` / `InteractableList.kt` — render the tab items and set `isEnteringGroup`.
- `TabGroupAction.kt` — definition of `NewGroupCreated`, `NewGroupAnimationPlayed`.
- `TabGroupActionReducer.kt` — sets/clears `enteringGroupId`.
- `TabStorageMiddleware.kt` — dispatches `NewGroupCreated` after the group is persisted.