Skip to main content

Animations

Hosanna UI offers three complementary animation layers. Start simple, then move up as you need more power.

ViewAnimator wraps one renderer, ViewJsonAnimator interpolates concrete renderer fields, and HosannaViewAnimator maps logical view state onto that engineViewAnimator wraps one renderer, ViewJsonAnimator interpolates concrete renderer fields, and HosannaViewAnimator maps logical view state onto that engine

Aggregate View Transitions

  • Screen-level transitions for NavController: Simple, Fade, and Slide.
  • Configured via app.config.json keys; optionally override per call.
  • Best for push/pop navigation while preserving aggregate-view lifecycle.

TabController changes the active tab directly; it does not run an aggregate transition for tab selection.

See: Aggregate View Transitions

ViewJsonAnimator (low-level)

  • Fast renderer-field tweening using JSON maps (many nodes at once).
  • Ideal for simple property animations where you control precise fields.
  • Pair with ViewAnimator for a single renderer.

See: ViewJsonAnimator

HosannaViewAnimator (high-level)

  • State-driven animations across a view subtree using logical state keys.
  • Automatically maps logical fields to renderer updates and persists final state.
  • Best for orchestrated UI effects spanning multiple views.

See: HosannaViewAnimator

HosannaViewAnimationRig

Source: hosanna-ui-samples-public/src/hosanna-ui-examples/rigs/animation/HosannaViewAnimationRig.ts
@view('HosannaViewAnimationRig')
export class HosannaViewAnimationRigView extends BaseExampleScreenView<HosannaViewAnimationRigState> {

  protected override getViews(): ViewStruct<ViewState>[] {
    const controlRow = HGroup([
      Label({
        id: 'title',
        text: 'HosannaViewAnimator: Animate 20 Buttons',
      }),
      Spacer({ width: 30 }),
      Button({ id: 'hideAll', text: 'Hide All', width: 180, height: 48 })
        .onClick(this.onHideAll.bind(this)),
      Button({ id: 'showAll', text: 'Show All', width: 180, height: 48 })
        .onClick(this.onShowAll.bind(this)),
      Button({ id: 'cycleColors', text: 'Cycle Colors', width: 180, height: 48 })
        .onClick(this.onCycleColors.bind(this)),
    ]).itemSpacing(18).vertAlignment(SGVerticalAlignment.Center);

    const buttons: ViewStruct<ViewState>[] = [];
    for (let i = 1; i <= 20; i++) {
      buttons.push(
        Button({
          id: `btn${i}`,
          text: `Button ${i}`,
          width: 260,
          height: 60,
        })
      );
    }

    const grid = GridGroup(buttons).columns(4).itemSpacing(18);

    return [
      VGroup([
        controlRow,
        Spacer({ height: 30 }),
        grid,
      ])
        .translation([60, 60])
        .itemSpacing(24),
    ];
  }

  private onHideAll(_event: ButtonEvent) {
    const anims: Record<string, Partial<ViewState>> = {};
    for (let i = 1; i <= 20; i++) {
      anims[`btn${i}`] = {
        labelOpacity: 0.0,
        backgroundColor: '#696969',
      } as unknown as Partial<ViewState>;
    }
    this.animateViews(anims, 400);
  }

  private onShowAll(_event: ButtonEvent) {
    const anims: Record<string, Partial<ViewState>> = {};
    for (let i = 1; i <= 20; i++) {
      anims[`btn${i}`] = {
        labelOpacity: 1.0,
        backgroundColor: '#ff0000',
      } as unknown as Partial<ViewState>;
    }
    this.animateViews(anims, 400);
  }

  private onCycleColors(_event: ButtonEvent) {
    const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff'];
    const anims: Record<string, Partial<ViewState>> = {};
    for (let i = 1; i <= 20; i++) {
      const colorIndex = (i - 1) % colors.length;
      anims[`btn${i}`] = {
        backgroundColor: colors[colorIndex],
      } as unknown as Partial<ViewState>;
    }
    // Example: Using onCompleted callback to log when animation finishes
    this.animateViews(anims, 600, () => {
      console.info('[HosannaViewAnimationRig] Color cycle animation completed!');
    });
  }
}
Talk to us