Mobilutveckling med Flutter · Lektion

Tillståndsmaskiner och indata­styrd rörelse

Koppla Rives tillståndsmaskinsindata till appdata för reaktiva animationer med förgreningar.

Lektion 2 av 413 steg

Tillståndsmaskiner och indata­styrd rörelse är en gratis lektion i Mobilutveckling med Flutter på CoddyKit. Detta är lektion 2 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Mobilutveckling med Flutter, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Mobilutveckling med Flutter innehåller totalt 4 lektioner.

Varför tillståndsmaskiner slår linjära animationer

Traditionella Rive-animationer spelar upp en enda tidslinje från början till slut. En tillståndsmaskin är en graf med animeringstillstånd som är sammankopplade med övergångar. Övergångarna utlöses av indata som du styr från Dart.

  • Reaktiv: animationen återspeglar aktuell data från appen i stället för en fast sekvens.
  • Förgrenad: samma artboard kan vara inaktiv, reagera på hovring, visa lyckat resultat eller misslyckande beroende på indatavärden.
  • Utjämnad: Rive interpolerar mellan tillstånd, så du får mjuka rörelser utan extra arbete.

I den här lektionen kopplar du en Rive-tillståndsmaskins indata till ditt widgetträd, så att rörelserna följer användarens åtgärder och modellens tillstånd.

De tre indatatyperna

En Rive-tillståndsmaskin tillhandahåller exakt tre typer av indata, och det centrala designbeslutet är att välja rätt typ.

  • Booleskt värde (SMIBool): ett kvarstående av/på-värde, till exempel isOpen eller isLoading.
  • Tal (SMINumber): ett kontinuerligt värde som kan styra blandningstillstånd, till exempel en förloppsnivå från 0 till 100 eller en rullningsförskjutning.
  • Trigger (SMITrigger): en engångspuls som utlöser en övergång en gång, till exempel tapSuccess. Den har inget bestående värde.

En bra tumregel är att använda en Trigger för tillfälliga händelser, ett Boolean för tillstånd som ska ligga kvar och ett Number för kontinuerliga värden som ska blandas.

Läsa in artboarden och tillståndsmaskinen

För att styra indata behöver du en StateMachineController. Callbacken onInit i RiveAnimation.asset ger dig den inlästa Artboard. Du skapar en controller från en namngiven tillståndsmaskin och kopplar in den.

Hämta alltid referenser till de indata du behöver just här, medan du fortfarande har artboarden, och spara dem i widgetens tillstånd.

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,
    );
  }
}

Ange ett booleskt indata vid tryckning

När du har en referens till en SMIBool styr du den helt enkelt genom att tilldela dess value. Rive utvärderar omedelbart övergångar vars villkor beror på detta indata.

Omslut Rive-widgeten med en GestureDetector och växla det booleska värdet. Du behöver inte anropa setState för själva animationen – Rive ritar om internt – men du kan behöva göra det för att hålla din egen modell synkroniserad.

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,
  ),
)

Utlösa en Trigger för en engångsrörelse

En SMITrigger har inget value. Anropa fire() för att pulsera den exakt en gång. Det passar perfekt för konfettiexplosioner, bockmarkeringar vid lyckat resultat eller skakfeedback vid fel.

Eftersom triggers återställs automatiskt behöver du aldrig rensa dem. Om du utlöser den igen spelas övergången upp på nytt.

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();
    }
  }
}

Styra ett talindata för blandade tillstånd

Number-inputar är särskilt användbara när en rörelse måste följa ett kontinuerligt värde. Ett blend-tillstånd i Rive kan omforma en rityta över intervallet 0–100, så om du matar det med aktuella data skapas ett mjukt, datastyrt rörelsemönster.

Vanliga källor är nedladdningsförlopp, en rullningsposition, ett skjutreglage eller en sensoravläsning. Begränsa värdet så att det aldrig överskrider det intervall som designern har avsett.

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);
  }
}

Ansluta indata till en dataström från appen

Indatastyrda rörelser blir kraftfulla när en dataström matar dem. Prenumerera i initState, skicka varje händelse till motsvarande indata och avsluta prenumerationen i dispose för att undvika läckor.

Observera att du skyddar dig mot null-indata: strömmen kan skicka en händelse innan onInit har körts, så ett null-medvetet anrop håller koden säker.

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();
}

Modellera förgreningslogik i ren Dart

Innan du arbetar med Rive är det bra att modellera beslutet som väljer en gren i vanlig Dart. Här är en fristående mapper som omvandlar resultatet av en uppladdning till den typ av input-händelse som du skulle utlösa. Genom att testa denna logik separat kan du hålla din widget tunn.

Det här kodexemplet har inget beroende av Flutter eller Rive och kan därför köras var som helst.

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
}

Tillämpa ett grenbeslut på indata

Bind nu det rena beslutet till state machine. En enda metod översätter en MotionEvent till rätt kombination av booleska lägen och triggerpulser.

  • Booleans behåller ett tillstånd, så ange både det nya värdet och nollställ motsatsen där det behövs.
  • Triggers aktiveras en gång för tillfällig återkoppling.

Genom att samla detta i en enda metod blir din indatakoppling konsekvent och enkel att granska.

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 tillbaka indatatillstånd via lyssnare

Ibland styrs rörelsen av Rive självt, till exempel en dragbar knapp som användaren flyttar på ritytan, och då behöver du få tillbaka det aktuella värdet i Dart. Indata exponerar sitt värde, och du kan läsa av det eller reagera på det i state machine-controllerns ändringscallback.

StateMachineController accepterar även en onStateChange-callback, så att du kan köra Dart när det aktiva tillståndet ändras. Det är användbart för analys eller för att kedja sidoeffekter.

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?;
  }
}

Livscykel, frigöring och vanliga fallgropar

State machines är resurser som behåller tillstånd. Hantera dem med samma noggrannhet som controllers och prenumerationer.

  • Frigör alltid StateMachineController i dispose() för att frigöra kopplingen till ritytan.
  • Kontrollera om indata är null: findSMI/findInput returnerar null om namnet är felstavat eller om ritytan saknar indata – annars blir anropet tyst verkningslöst.
  • Matcha namnen exakt: namn på indata och state machines är skiftlägeskänsliga och måste överensstämma med filen .riv.
  • Anropa inte setState för ofta: Rive ritar om sig självt; att bygga om hela underträdet vid varje tick från en dataström slösar bildrutor.

Om du gör detta rätt förblir dina indatastyrda rörelser mjuka och fria från läckor.

Snabbkontroll: Välj rätt indata

Du kopplar en Rive state machine till en knapp för att skicka ett formulär. När användaren trycker på Skicka ska en bekräftelseanimation med en bock spelas upp en gång och sedan återgå till viloläget. Vilken indatatyp och vilket anrop är rätt?

Sammanfattning: Reaktiva, förgrenade rörelser

Nu vet du hur du får Rive-animationer att följa appen i stället för en fast tidslinje.

  • Tre typer av indata: Boolean för ett kvarstående tillstånd, Number för kontinuerliga eller blandade värden och Trigger för engångshändelser.
  • Koppling: skapa en StateMachineController i onInit, koppla den till ritytan och spara referenserna till indata.
  • Styrning: tilldela value för booleans och tal, anropa fire() för triggers och mata kontinuerliga data från strömmar med begränsning.
  • Arkitektur: modellera grenbeslutet i ren, testbar Dart och översätt det sedan till indata i en central metod.
  • Ordning: frigör controllern, avsluta prenumerationer, kontrollera indata mot null och matcha namnen exakt.

Med dessa mönster blir dina animationer en direkt återspegling av användarens indata och modellens tillstånd.

Gratis att börja

Lär dig Dart med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
22
Lektioner
88

Vanliga frågor

Är lektionen ”Tillståndsmaskiner och indata­styrd rörelse” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Mobilutveckling med Flutter, inklusive ”Tillståndsmaskiner och indata­styrd rörelse”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Mobilutveckling med Flutter innehåller totalt 4 lektioner.

Vad lär jag mig i ”Tillståndsmaskiner och indata­styrd rörelse”?

Koppla Rives tillståndsmaskinsindata till appdata för reaktiva animationer med förgreningar. Ni övar på Mobilutveckling med Flutter med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Mobilutveckling med Flutter?

Du behöver inga förkunskaper. Utbildningen i Mobilutveckling med Flutter på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.

Hur lång tid tar lektionen ”Tillståndsmaskiner och indata­styrd rörelse”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Mobilutveckling med Flutter-lektionen?

Ja. Varje Mobilutveckling med Flutter-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Bädda in Rive-resurser och styrenheter
  2. Tillståndsmaskiner och indata­styrd rörelse
  3. Hero-övergångar och rörelse för delade element
  4. Förskjutna och koreograferade AnimationControllers
← Tillbaka till Mobilutveckling med Flutter