Skip to main content

Module loading_indicator

Module loading_indicator 

Source
Expand description

The Material 3 Expressive loading indicator: a continuously-morphing, continuously-rotating sequence of seven Material shapes, driven by the crate’s Morph feature-point engine.

§Attribution

Ported from paadevelopments/material_3_expressive v1.0.8 (MIT, © 2026 Paa Developments), lib/components/loading_indicator/ — whose components/m3e_expressive_loading_indicator.dart is itself, per that file’s own header, vendored verbatim from the loading_indicator_m3e package, a Dart port of AndroidX Compose Material3’s LoadingIndicator.kt (androidx/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/LoadingIndicator.kt, © 2024 The Android Open Source Project, Apache License 2.0). The shape sequence, timing constants, spring, and rotation choreography below are transcribed from that Apache-2.0-licensed logic, retargeted onto this crate’s own shapes::Morph/RoundedPolygon engine rather than Flutter’s. The container variant and its theme defaults (lib/components/loading_indicator/{m3e_loading_indicator,styles/m3e_loading_indicator_theme}.dart) are the MIT-licensed material_3_expressive wrapper around it. See plugins/material/NOTICE’s Apache License, Version 2.0 — The Android Open Source Project section (this module activates its previously-planned loading-indicator entry).

§Shape sequence

[SHAPE_CYCLE] is upstream’s _defaultPolygons (m3e_expressive_loading_indicator.dart:57-65): softBurst, cookie9Sided, pentagon, pill, sunny, cookie4Sided, oval, each resolved through this crate’s 35-shape ShapeKind catalog. [morph_sequence] builds the circular run of Morphs between consecutive shapes (_createMorphSequence(polygons, circularSequence: true), m3e_expressive_loading_indicator.dart:195-211) once and shares it across every indicator instance — building a Morph runs the expensive corner-measure/match pipeline (see Morph’s own cost note).

§Choreography

Two independent clocks drive the paint, matching m3e_expressive_loading_indicator.dart:96-166’s build:

  • Per-cycle morph. Every [PER_SHAPE_PERIOD] (650ms — upstream’s _morphIntervalMs), the indicator advances to the next morph in [morph_sequence] and restarts a spring-driven progress from 0 toward 1 — [LOADING_SPRING] (M3EMotion.expressiveSpatialDefault, m3e_motion.dart:104-107, promoted to this crate’s shared MaterialSpring::EXPRESSIVE_SPATIAL_DEFAULT token) released with [MORPH_INITIAL_VELOCITY] (SpringSimulation(spring, 0, 1, 5, snapToEnd: true), m3e_expressive_loading_indicator.dart:85-91).
  • Continuous background rotation. A separate 0.0..1.0 loop repeats every [GLOBAL_ROTATION_PERIOD] (4666ms — upstream’s _globalRotationDurationMs), contributing up to a full turn.

The paint reads both, plus a per-cycle accumulator (rotation_target_deg, upstream’s _morphRotationTargetAngle) that steps by [QUARTER_ROTATION_DEG] (90°) modulo [FULL_ROTATION_DEG] every cycle, and sums them exactly as upstream’s totalRotationDegrees does — so the shape visibly spins a quarter-turn per morph on top of the slow continuous background spin.

§Initial cycle

Upstream’s initState calls _startMorphCycle() once, synchronously, before the first frame ever paints (m3e_expressive_loading_indicator.dart:192,259). That call already advances _currentMorphIndex from 0 to 1 and _morphRotationTargetAngle from 90° to 180° — so the very first shape ever shown is [SHAPE_CYCLE][1] (cookie9Sided), not [0] (softBurst); [0] only reappears later in the loop. LoadingIndicatorWidget::advance_cycle replays this by running once at the end of build, matching the quirk exactly rather than “fixing” it.

§Geometry

Every catalog shape is normalized into (0,0)..(1,1) (crate::shapes::RoundedPolygon::normalized), so a raw Morph::path output needs scaling and centering before it paints. [shape_cycle_scale_factor] is upstream’s _calculateScaleFactor (m3e_expressive_loading_indicator.dart:213-247): since the shape rotates, sizing it to fit only its own (unrotated) bounds would clip a corner as it turns, so the scale is instead derived from the worst-case ratio between each shape’s axis-aligned bounds and its maximum any-rotation bounds (crate::shapes::RoundedPolygon::max_bounds) across the whole cycle. [process_path] applies that scale (further scaled by [ACTIVE_INDICATOR_SIZE] against the container box — upstream’s activeIndicatorScale), centers the result, and rotates it about that same center — the upstream _MorphPainter._processPath scale+center composed with the enclosing Transform.rotate (m3e_expressive_loading_indicator.dart:144-159, 319-340).

§Variants

LoadingIndicatorVariant::Default paints only the floating shape (containerColorDefault, fully transparent — no background is drawn at all). LoadingIndicatorVariant::Contained additionally fills a fully-rounded container the same size as the indicator box behind it, colors.primary_container/colors.on_primary_container in place of colors.primary — upstream’s M3ELoadingIndicatorTheme (m3e_loading_indicator_theme.dart). The container’s radius (999, i.e. always fully rounded at this box size) and its zero default padding mean the indicator’s own geometry is unchanged between variants.

§Dropped frames

The per-cycle boundary is detected the same way the pre-rework module detected it: a linear 0.0..1.0 repeat whose wrap (this frame’s phase < last frame’s) triggers LoadingIndicatorWidget::advance_cycle, rather than a counted timer callback. A dropped frame longer than one full period therefore skips a cycle visually rather than catching every intermediate one — acceptable for a decorative loading spinner, and it always self-corrects on the next frame.

Structs§

LoadingIndicatorView
A Material 3 Expressive loading indicator. See the module docs.
LoadingIndicatorWidget
The retained widget for a LoadingIndicatorView. See the module docs.

Enums§

LoadingIndicatorVariant
The indicator’s visual variant — upstream’s M3ELoadingIndicatorVariant (m3e_loading_indicator_variant.dart).

Functions§

LoadingIndicator
PascalCase alias for loading_indicator, matching the widget-fn vocabulary (Button/Image/…).
loading_indicator
Create a loading indicator (LoadingIndicatorVariant::Default).