Mobiludvikling med Flutter · Lektion

State machines og inputstyret bevægelse

Forbind input fra Rive-state machines med appdata for reaktive animationer med forgreninger.

Lektion 2 af 413 trin

State machines og inputstyret bevægelse er en gratis Mobiludvikling med Flutter-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Mobiludvikling med Flutter, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.

Hvorfor tilstandsmaskiner er bedre end lineære animationer

Traditionelle Rive-animationer afspiller én tidslinje fra start til slut. En tilstandsmaskine er en graf af animationstilstande, der er forbundet med overgange, og disse overgange udløses af inputværdier, som du styrer fra Dart.

  • Reaktiv: Animationen afspejler aktuelle data fra appen i stedet for en fast sekvens.
  • Forgrening: Den samme tegneflade kan være inaktiv, reagere på markøren, vise succes eller vise fejl afhængigt af inputværdierne.
  • Blød overgang: Rive interpolerer mellem tilstande, så du får jævn bevægelse uden ekstra arbejde.

I denne lektion kobler du inputværdierne i en Rive-tilstandsmaskine til dit widgettræ, så bevægelserne følger brugerhandlinger og modellens tilstand.

De tre inputtyper

En Rive-tilstandsmaskine stiller præcis tre slags inputværdier til rådighed, og valget af den rigtige type er den centrale designbeslutning.

  • Boolesk værdi (SMIBool): En fastholdt til/fra-værdi, f.eks. isOpen eller isLoading.
  • Tal (SMINumber): En kontinuerlig værdi, der kan styre blandingstilstande, f.eks. en fremdrift fra 0 til 100 eller en rulleforskydning.
  • Udløser (SMITrigger): En enkeltstående impuls, der udløser en overgang én gang, f.eks. tapSuccess. Den har ingen vedvarende værdi.

Tommelfingerregel: Brug en udløser til øjeblikkelige hændelser, en boolesk værdi til en tilstand, du fastholder, og et tal til alt kontinuerligt, som du vil blande.

Indlæsning af tegnefladen og tilstandsmaskinen

For at styre inputværdier skal du bruge en StateMachineController. Callbacken onInit fra RiveAnimation.asset giver dig den indlæste Artboard. Du opretter en styreenhed ud fra en navngiven tilstandsmaskine og tilknytter den.

Hent altid referencer til de inputværdier, du har brug for, netop her, mens du stadig har tegnefladen, og gem dem i din widgets tilstand.

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class LikeButton extends StatefulWidget {
  const LikeButton({super.key});
  @override
  State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  StateMachineController? _controller;
  SMIBool? _isLiked;

  void _onRiveInit(Artboard artboard) {
    final controller = StateMachineController.fromArtboard(
      artboard,
      'LikeMachine',
    );
    if (controller != null) {
      artboard.addController(controller);
      _controller = controller;
      _isLiked = controller.findInput<bool>('isLiked') as SMIBool?;
    }
  }

  @override
  Widget build(BuildContext context) {
    return RiveAnimation.asset(
      'assets/like.riv',
      stateMachines: const ['LikeMachine'],
      onInit: _onRiveInit,
    );
  }
}

Indstilling af en boolesk inputværdi ved tryk

Når du har en reference til en SMIBool, styrer du den blot ved at tildele dens value. Rive evaluerer straks de overgange, hvis betingelse afhænger af denne inputværdi.

Pak Rive-widgetten ind i en GestureDetector, og skift den booleske værdi. Du behøver ikke kalde setState for selve animationen — Rive tegner internt igen — men du kan få brug for det for at holde din egen model synkroniseret.

GestureDetector(
  onTap: () {
    final input = _isLiked;
    if (input != null) {
      input.value = !input.value; // flips the held state
    }
  },
  child: RiveAnimation.asset(
    'assets/like.riv',
    stateMachines: const ['LikeMachine'],
    onInit: _onRiveInit,
  ),
)

Udløsning af en udløser til en enkeltstående bevægelse

En SMITrigger har ingen value. Du kalder fire() for at sende en impuls præcis én gang, hvilket er perfekt til konfettieksplosioner, succesmarkeringer eller rystelser ved fejl.

Da udløsere nulstilles automatisk, behøver du aldrig rydde dem. Hvis du udløser igen, afspilles overgangen på ny.

class _SubmitButtonState extends State<SubmitButton> {
  SMITrigger? _success;
  SMITrigger? _error;

  void _onInit(Artboard artboard) {
    final c = StateMachineController.fromArtboard(artboard, 'SubmitMachine');
    if (c != null) {
      artboard.addController(c);
      _success = c.findSMI('success') as SMITrigger?;
      _error = c.findSMI('error') as SMITrigger?;
    }
  }

  Future<void> _onSubmit() async {
    final ok = await _saveForm();
    if (ok) {
      _success?.fire();
    } else {
      _error?.fire();
    }
  }
}

Styring af en talinputværdi til blandede tilstande

Talinput er ideelle, når bevægelsen skal følge en kontinuerlig værdi. En blend-tilstand i Rive kan ændre et artboard gradvist over området 0–100, så live-data giver flydende, datadrevet bevægelse.

Almindelige kilder er downloadfremdrift, en rulleposition, en skyder eller en sensormåling. Begræns værdien, så den aldrig overskrider det område, kunstneren har designet.

class _ProgressGaugeState extends State<ProgressGauge> {
  SMINumber? _progress;

  void _onInit(Artboard artboard) {
    final c = StateMachineController.fromArtboard(artboard, 'GaugeMachine');
    if (c != null) {
      artboard.addController(c);
      _progress = c.findSMI('progress') as SMINumber?;
    }
  }

  void updateProgress(double fraction) {
    // fraction is 0.0..1.0 from a download stream
    _progress?.value = (fraction * 100).clamp(0, 100);
  }
}

Forbindelse af input til en strøm af appdata

Inputstyret bevægelse bliver virkelig effektiv, når en datastrøm leverer data til den. Abonner i initState, send hver hændelse ind i det tilsvarende input, og annullér abonnementet i dispose for at undgå lækager.

Bemærk, at du beskytter dig mod et null-input: Datastrømmen kan udsende data, før onInit er kørt, så et null-bevidst kald holder dig på den sikre side.

late final StreamSubscription<double> _sub;

@override
void initState() {
  super.initState();
  _sub = downloadProgress$.listen((fraction) {
    _progress?.value = (fraction * 100).clamp(0, 100);
  });
}

@override
void dispose() {
  _sub.cancel();
  _controller?.dispose();
  super.dispose();
}

Modellering af forgreningslogik i ren Dart

Før du tager fat på Rive, er det nyttigt at modellere den beslutning, der vælger en gren, som almindelig Dart. Her er en selvstændig mapper, der omdanner et uploadresultat til den type inputhændelse, du ville udløse. Når du tester denne logik isoleret, kan din widget holdes enkel.

Dette kodestykke har ingen Flutter- eller Rive-afhængighed, så det kan køre overalt.

enum MotionEvent { idle, loading, success, error }

MotionEvent selectMotion({
  required bool inFlight,
  required bool? succeeded,
}) {
  if (inFlight) return MotionEvent.loading;
  if (succeeded == null) return MotionEvent.idle;
  return succeeded ? MotionEvent.success : MotionEvent.error;
}

void main() {
  print(selectMotion(inFlight: true, succeeded: null));   // loading
  print(selectMotion(inFlight: false, succeeded: true));  // success
  print(selectMotion(inFlight: false, succeeded: false)); // error
  print(selectMotion(inFlight: false, succeeded: null));  // idle
}

Anvendelse af en grenbeslutning på input

Knyt nu den rene beslutning til tilstandsmaskinen. En enkelt metode oversætter en MotionEvent til den korrekte kombination af booleske tilstande og triggerimpulser.

  • Booleske værdier fastholder en tilstand, så indstil både den nye værdi og ryd den modsatte værdi, hvor det er nødvendigt.
  • Triggere udløses én gang for midlertidig feedback.

Ved at samle dette i én metode bliver din inputforbindelse ensartet og nem at gennemgå.

void applyMotion(MotionEvent event) {
  switch (event) {
    case MotionEvent.loading:
      _isLoading?.value = true;
      break;
    case MotionEvent.idle:
      _isLoading?.value = false;
      break;
    case MotionEvent.success:
      _isLoading?.value = false;
      _success?.fire();
      break;
    case MotionEvent.error:
      _isLoading?.value = false;
      _error?.fire();
      break;
  }
}

Læs inputtilstanden tilbage via lyttere

Nogle gange styres bevægelsen af Rive selv, for eksempel en trækbar knap, som brugeren flytter på artboardet, og så har du brug for at få den aktuelle værdi tilbage i Dart. Input viser deres værdi, og du kan enten hente den løbende eller reagere på den i tilstandsmaskinens ændringscallback.

StateMachineController accepterer også en onStateChange-callback, så du kan køre Dart, hver gang den aktive tilstand skifter. Det er nyttigt til analyse eller kædning af sideeffekter.

void _onInit(Artboard artboard) {
  final c = StateMachineController.fromArtboard(
    artboard,
    'KnobMachine',
    onStateChange: (machineName, stateName) {
      debugPrint('Entered $stateName in $machineName');
      if (stateName == 'Snapped') {
        HapticFeedback.lightImpact();
      }
    },
  );
  if (c != null) {
    artboard.addController(c);
    _angle = c.findSMI('angle') as SMINumber?;
  }
}

Livscyklus, frigivelse og almindelige faldgruber

Tilstandsmaskiner er ressourcer med en tilstand. Behandl dem med samme disciplin som controllere og abonnementer.

  • Frigiv altid StateMachineController i dispose() for at frigive bindingen til artboardet.
  • Kontrollér input for null: findSMI/findInput returnerer null, hvis navnet er stavet forkert, eller artboardet ikke har det pågældende input — ellers sker der ingenting uden en fejl.
  • Match navne nøjagtigt: Navne på input og tilstandsmaskiner skelner mellem store og små bogstaver og skal stemme overens med .riv-filen.
  • Undgå for mange kald til setState: Rive tegner sig selv igen; det spilder frames at genopbygge hele undertræet ved hvert tick fra datastrømmen.

Når du gør dette korrekt, forbliver din inputstyrede bevægelse jævn og fri for lækager.

Hurtigt tjek: Vælg det rigtige input

Du forbinder en Rive-tilstandsmaskine med en knap til afsendelse af en formular. Når brugeren trykker på knappen, skal der afspilles en succesanimation med et flueben én gang, hvorefter den vender tilbage til inaktiv tilstand. Hvilken inputtype og hvilket kald er korrekt?

Opsummering: Reaktiv, forgrenet bevægelse

Du ved nu, hvordan du får Rive-animationer til at følge din app i stedet for en fast tidslinje.

  • Tre inputtyper: Boolean til en fastholdt tilstand, Number til kontinuerlige eller blendede værdier og Trigger til engangshændelser.
  • Forbindelse: Opret en StateMachineController i onInit, tilknyt den til artboardet, og gem referencer til input.
  • Styring: Tildel til value for booleske værdier og tal, kald fire() for triggere, og send kontinuerlige data fra datastrømme med begrænsning.
  • Arkitektur: Modellér grenbeslutningen i ren, testbar Dart, og oversæt den derefter til input i én central metode.
  • Vedligeholdelse: Frigiv controlleren, annullér abonnementer, kontrollér input for null, og match navne nøjagtigt.

Med disse mønstre bliver dine animationer en levende afspejling af brugerinput og modeltilstand.

Gratis at komme i gang

Lær Dart med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
22
Lektioner
88

Ofte stillede spørgsmål

Er lektionen “State machines og inputstyret bevægelse” gratis?

Ja — alle 3 lektioner i læringssporet Mobiludvikling med Flutter, inklusive “State machines og inputstyret bevægelse”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Mobiludvikling med Flutter-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “State machines og inputstyret bevægelse”?

Forbind input fra Rive-state machines med appdata for reaktive animationer med forgreninger. Du øver dig i Mobiludvikling med Flutter med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Mobiludvikling med Flutter?

Der kræves ingen tidligere erfaring. Mobiludvikling med Flutter på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “State machines og inputstyret bevægelse”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Mobiludvikling med Flutter-lektion?

Ja. Alle Mobiludvikling med Flutter-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Indlejring af Rive-assets og controllere
  2. State machines og inputstyret bevægelse
  3. Hero-overgange og bevægelse af delte elementer
  4. Forskudte og koreograferede AnimationControllers
← Tilbage til Mobiludvikling med Flutter