1 — Pulsing session dot (live)
Current spec — alpha only
Live
Recent
Dead
<objectAnimator
android:propertyName="alpha"
android:valueFrom="1.0"
android:valueTo="0.35"
android:duration="900"
android:interpolator="@anim/ease_in_out"
android:repeatMode="reverse"
android:repeatCount="infinite"/>
Current: alpha 1.0 → 0.35, ease-in-out, 900ms, reversed infinite. Confirmed — keep this timing, it's unobtrusive and semantically clear.
Proposed enhancement — rings variant (optional)
With rings
Alpha only
// CSS equivalent — Android uses box-shadow property
@keyframes pulse-rings {
0% { box-shadow: 0 0 0 0 rgba(63,185,80,0.5); }
70% { box-shadow: 0 0 0 10px rgba(63,185,80,0); }
100% { box-shadow: 0 0 0 0 rgba(63,185,80,0); }
}
// duration: 1800ms (2× the alpha cycle)
// In Android: ValueAnimator on custom View
The ring variant is more "alive" but more visually heavy. Recommend keeping alpha-only for the drawer (dense list) and using rings only on the status pill in the pane header where it has breathing room.
2 — Drawer slide-in / out
Live demo — hover to open
// DrawerLayout onDrawerSlide interpolation
<translate
android:fromXDelta="-100%"
android:toXDelta="0"
android:duration="280"
android:interpolator=
"@interpolator/fast_out_slow_in"/>
cubic-bezier(0.4, 0.0, 0.2, 1) — Material Design "Standard" easing. Fast out of the start position, decelerates gently to rest. 280ms — snappy but not jarring. This is already the Android DrawerLayout default; explicitly specify to prevent platform version drift.
Easing curve
Fast out, Slow in — cubic-bezier(0.4, 0.0, 0.2, 1)
Duration
280ms
Easing
fast_out_slow_in
Dismiss (swipe close)
linear_out_slow_in 200ms
3 — Keybar slide-in (terminal pane focus)
Live demo — hover to show keybar
// Fires when: terminal pane becomes active pane
<translate
android:fromYDelta="100%"
android:toYDelta="0"
android:duration="220"
android:interpolator=
"@interpolator/linear_out_slow_in"/>
// Dismiss (code-server pane focused):
<translate
android:fromYDelta="0"
android:toYDelta="100%"
android:duration="180"
android:interpolator=
"@interpolator/fast_out_linear_in"/>
220ms appear, 180ms dismiss. Decelerate on enter (linear_out_slow_in) — gives the bar a sense of weight landing. Accelerate on exit (fast_out_linear_in) — snaps out of the way quickly.
Trigger conditions
Show keybar when:
Active pane changes to a PaneType.TERMINAL pane. Also show on first terminal pane add.
Hide keybar when:
Active pane changes to non-terminal. Also hide when IME (soft keyboard) is up on Android 12+ via WindowInsetsController.
No animation when:
Switching between two terminal panes — keybar stays put, no animation triggered.
4 — Pane add / remove transition
Pane enters from the right edge
Before (2 panes)
pane 1
pane 2
+ Add pane 3 →
After (3 panes, pane 3 animated in)
pane 1
pane 2
pane 3
// New pane view enters from right, existing panes compress
ValueAnimator.ofFloat(0f, finalWeight).apply {
duration = 300
interpolator = FastOutSlowInInterpolator()
addUpdateListener { layoutParams.weight = it.animatedValue as Float
requestLayout() }
}.start()
// New pane alpha fade-in
newPaneView.alpha = 0f
newPaneView.animate()
.alpha(1f)
.setDuration(250)
.setStartDelay(50)
.setInterpolator(FastOutSlowInInterpolator())
.start()
Existing panes shrink their weight proportionally as the new pane expands from zero weight. The simultaneous weight animation + alpha fade-in takes 300ms total. No slide from offscreen — it grows in place from the right.
Status pill state transitions
All 4 states — 300ms cross-fade between them:
live
→
idle
→
stale
→
disc
// Status pill: cross-fade between state drawables
TransitionDrawable([fromDrawable, toDrawable]).also {
pillView.background = it
it.startTransition(300)
}
// Dot color: ValueAnimator on textColor + drawable tint
// Duration: 300ms, FastOutSlowIn
// Pulsing dot (live state only):
// Start ObjectAnimator on alpha when state == LIVE
// Cancel animator on state change from LIVE
State change rule: live→idle is immediate (instant, no animation delay). idle→disconnected shows a 500ms shake micro-animation on the dot before going red, so the user catches the change. disconnected→idle/live fades normally.
Animation spec summary
| Animation | Duration | Easing | Property | Notes |
|---|---|---|---|---|
| Session dot pulse | 900ms | ease-in-out | alpha 1.0 → 0.35, reversed ∞ | Live sessions only. Yellow/dead = static. |
| Drawer open | 280ms | fast_out_slow_in | translateX -100% → 0 | Material Standard easing. |
| Drawer close | 200ms | linear_out_slow_in | translateX 0 → -100% | Faster dismiss than open. |
| Keybar appear | 220ms | linear_out_slow_in | translateY 100% → 0 | Terminal pane activated. |
| Keybar dismiss | 180ms | fast_out_linear_in | translateY 0 → 100% | Non-terminal pane activated. |
| Pane add | 300ms | fast_out_slow_in | weight 0→final + alpha 0→1 (+50ms delay) | Weight via ValueAnimator + requestLayout. |
| Pane remove | 200ms | fast_out_linear_in | alpha 1→0 then weight collapse 250ms | Fade first, then weight collapse. |
| Status pill state change | 300ms | fast_out_slow_in | background cross-fade + dot color | idle→disc adds 500ms shake before red. |