Flutter: een quizspel bouwen

UPDATE (01/06/2019): u kunt hier een alternatieve versie vinden met behulp van het rebuilder-pakket.

Invoering

In dit artikel wil ik je laten zien hoe ik dit voorbeeld van trivia-spel met Flutter en het frideos-pakket heb gebouwd (bekijk deze twee voorbeelden om te leren hoe het werkt voorbeeld1, voorbeeld2). Het is een vrij eenvoudig spel, maar het bevat verschillende interessante argumenten.

De app heeft vier schermen:

  • Een hoofdpagina waar de gebruiker een categorie kiest en het spel start.
  • Een instellingenpagina waar de gebruiker het aantal vragen, het type database (lokaal of extern), de tijdslimiet voor elke vraag en de moeilijkheidsgraad kan selecteren.
  • Een trivia-pagina waar de vragen, de score, het aantal correcties, fouten en niet-beantwoord worden weergegeven.
  • Een overzichtspagina met alle vragen met de juiste / foute antwoorden.

Dit is het eindresultaat:

Je kunt hier een betere gif zien.
  • Deel 1: Projectinstellingen
  • Deel 2: App-architectuur
  • Deel 3: API en JSON
  • Deel 4: startpagina en andere schermen
  • Deel 5: TriviaBloc
  • Deel 6: Animaties
  • Deel 7: Overzichtspagina
  • Gevolgtrekking
Deel 1 - Projectinstellingen

1 - Maak een nieuw flutterproject:

flutter maak je_projectnaam

2 - Bewerk het bestand "pubspec.yaml" en voeg de http- en frideos-pakketten toe:

afhankelijkheden:
  fladderen:
    sdk: fladderen
  http: ^ 0.12.0
  frideos: ^ 0.6.0

3- Verwijder de inhoud van het main.dart-bestand

4- Maak de projectstructuur als de volgende afbeelding:

Structuur details

  • API: hier zijn de dart-bestanden voor de API van de "Open trivia-database" en een nep-API voor lokale testen: api_interface.dart, mock_api.dart, trivia_api.dart.
  • Blokken: de plaats van de enige BLoC van de app trivia_bloc.dart.
  • Modellen: appstate.dart, category.dart, models.dart, question.dart, theme.dart, trivia_stats.dart.
  • Schermen: main_page.dart, settings_page.dart, summary_page.dart, trivia_page.dart.
Deel 2 - App-architectuur

In mijn laatste artikel schreef ik over verschillende manieren om gegevens over meerdere widgets en pagina's te verzenden en te delen. In dit geval gaan we een iets geavanceerdere aanpak gebruiken: een exemplaar van een singleton-klasse met de naam appState wordt aan de widgetsboom verstrekt met behulp van een InheritedWidget-provider (AppStateProvider), deze houdt de status van de app, sommige bedrijven logica en de instantie van de enige BLoC die het "quizgedeelte" van de app afhandelt. Dus uiteindelijk zal het een soort mix zijn tussen het singleton- en het BLoC-patroon.

Binnen elke widget is het mogelijk om de instantie van de AppState-klasse te krijgen door te bellen naar:

final appState = AppStateProvider.of  (context);

1 - main.dart

Dit is het beginpunt van de app. De klasse App is een stateloze widget waar deze wordt aangemerkt als de instantie van de AppState-klasse en die vervolgens, met behulp van de AppStateProvider, wordt verstrekt aan de widgetsboom. De appState-instantie wordt verwijderd, waarbij alle streams worden gesloten, in de verwijderingsmethode van de klasse AppStateProvider.

De MaterialApp-widget is verpakt in een ValueBuilder-widget, zodat elke keer dat een nieuw thema wordt geselecteerd, de hele widgetsboom opnieuw wordt opgebouwd en het thema wordt bijgewerkt.

2 - Staatsbeheer

Zoals eerder gezegd, heeft de appState-instantie de status van de app. Deze les wordt gebruikt voor:

  • Instellingen: huidig ​​thema gebruikt, laad / bewaar het met de SharedPreferences. API-implementatie, mock of remote (met behulp van de API van opentdb.com). De ingestelde tijd voor elke vraag.
  • Het huidige tabblad wordt weergegeven: hoofdpagina, trivia, samenvatting.
  • Vragen worden geladen.
  • (indien op externe API) Sla de instellingen van de categorie, het nummer en de moeilijkheidsgraad van de vragen op.

In de constructor van de klas:

  • _createThemes bouwt de thema's van de app.
  • _loadCategories laden de categorieën van de te kiezen vragen in de vervolgkeuzelijst van de hoofdpagina.
  • countdown is een StreamedTransformed van het frideos pakket van het type , gebruikt om uit het tekstveld de waarde te krijgen om het aftellen in te stellen.
  • QuestionsAmount bevat het aantal vragen dat tijdens het trivia-spel moet worden weergegeven (standaard 5).
  • Het exemplaar van de classTriviaBloc wordt geïnitialiseerd en geeft de streams door, de aftelprocedure, de lijst met vragen en de pagina die moet worden weergegeven.
Deel 3 - API en JSON

Om de gebruiker te laten kiezen tussen een lokale en een externe database, heb ik de QuestionApi-interface gemaakt met twee methoden en twee klassen die deze implementeren: MockApi en TriviaApi.

abstracte klasse VragenAPI {
  Toekomstige  getCategories (StreamedList  categorieën);
  
  Toekomstige  getQuestions (
    {StreamedList  vragen,
     int nummer,
     Categorie categorie,
     Vraag: Moeilijkheidsgraad,
     Type vraagtype});
}

De MockApi-implementatie is standaard ingesteld (deze kan worden gewijzigd op de instellingenpagina van de app) in de appState:

// API
VragenAPI api = MockAPI ();
final apiType = StreamedValue  (initialData: ApiType.mock);

Hoewel apiType slechts een samenvatting is voor het wijzigen van de database op de instellingenpagina:

enum ApiType {mock, remote}

mock_api.dart:

trivia_api.dart:

1 - API-selectie

Op de instellingenpagina kan de gebruiker selecteren welke database moet worden gebruikt via een vervolgkeuzelijst:

ValueBuilder  (
  gestreamd: appState.apiType,
  builder: (context, momentopname) {
    terug DropdownButton  (
      waarde: snapshot.data,
      onChanged: appState.setApiType,
      items: [
        const DropdownMenuItem  (
          waarde: ApiType.mock,
          child: Tekst (‘Demo’),
        )
        const DropdownMenuItem  (
          waarde: ApiType.remote,
          child: Tekst (‘opentdb.com’),
       )
    ]);
}),

Telkens wanneer een nieuwe database wordt geselecteerd, verandert de methode setApiType de implementatie van de API en worden de categorieën bijgewerkt.

void setApiType (ApiType type) {
  if (apiType.value! = type) {
    apiType.value = type;
    if (type == ApiType.mock) {
      api = MockAPI ();
    } anders {
      api = TriviaAPI ();
    }
    _loadCategories ();
  }
}

2 - Categorieën

Om de lijst met categorieën te krijgen, noemen we deze URL:

https://opentdb.com/api_category.php

Extract van reactie:

{"trivia_categories": [{"id": 9, "name": "Algemene kennis"}, {"id": 10, "name": "Entertainment: Boeken"}]

Dus na het decoderen van de JSON met behulp van de functie jsonDecode van de dart: convert library:

final jsonResponse = convert.jsonDecode (response.body);

we hebben deze structuur:

  • jsonResponse ['trivia_categories']: lijst met categorieën
  • jsonResponse ['trivia_categories'] [INDEX] ['id']: id van de categorie
  • jsonResponse ['trivia_categories'] [INDEX] ['name']: naam van de categorie

Het model zal dus zijn:

klasse Categorie {
  Categorie ({this.id, this.name});
  fabriek Category.fromJson (Map  json) {
    terugkeer Categorie (id: json [‘id’], naam: json [‘naam’]);
  }
  int id;
  String naam;
}

3 - Vragen

Als we deze URL noemen:

https://opentdb.com/api.php?amount=2&difficulty=medium&type=multiple

dit zal het antwoord zijn:

{"response_code": 0, "results": [{"category": "Entertainment: Music", "type": "multiple", "moeilijkheid": "medium", "question": "Welke Franse artiest / band staat bekend om het spelen op het midi-instrument & quot; Launchpad & quot;? "," correct_answer ":" Madeon "," incorrect_answers ": [" Daft Punk "," Disclosure "," David Guetta "]}, {" category ":" Sports "," type ":" multiple "," moeilijkheidsgraad ":" medium "," question ":" Wie heeft het 2015 National Football Playoff (CFP) National Championship gewonnen? "," Correct_answer ":" Ohio State Buckeyes "," incorrect_answers ": [" Alabama Crimson Tide "," Clemson Tigers "," Wisconsin Badgers "]}]}

In dit geval, bij het decoderen van de JSON, hebben we deze structuur:

  • jsonResponse ['results']: lijst met vragen.
  • jsonResponse ['results'] [INDEX] ['category']: de categorie van de vraag.
  • jsonResponse ['results'] [INDEX] ['type']: type vraag, meervoudig of boolean.
  • jsonResponse ['results'] [INDEX] ['question']: de vraag.
  • jsonResponse ['results'] [INDEX] ['correct_answer']: het juiste antwoord.
  • jsonResponse ['results'] [INDEX] ['incorrect_answers']: lijst met onjuiste antwoorden.

Model:

class QuestionModel {
  QuestionModel ({this.question, this.correctAnswer, this.incorrectAnantwoorden});
  fabriek QuestionModel.fromJson (Map  json) {
    Return QuestionModel (
      vraag: json [‘vraag’],
      correctAnswer: json [‘correct_answer’],
      incorrectAnantwoorden: (json [‘incorrect_answers’] als lijst)
        .map ((antwoord) => answer.toString ())
        .ToList ());
  }
  String vraag;
  String correctAntwoord;
  Lijst  incorrectAnantwoorden;
}

4 - TriviaApi-klasse

De klasse implementeert de twee methoden van de VragenApi-interface, getCategories en getQuestions:

  • Categorieën ophalen

In het eerste deel wordt de JSON gedecodeerd en vervolgens wordt het model met behulp van het model geparseerd en wordt een lijst van type Categorie verkregen. Uiteindelijk wordt het resultaat aan categorieën gegeven (een StreamedLijst van type Categorie die wordt gebruikt om de lijst met categorieën op de hoofdpagina te vullen ).

final jsonResponse = convert.jsonDecode (response.body);
eindresultaat = (jsonResponse [‘trivia_categories’] als lijst)
.map ((categorie) => Category.fromJson (categorie));
categories.value = [];
categorieën
..addAll (resultaat)
..addElement (Categorie (id: 0, naam: ‘Elke categorie’));
  • Vragen krijgen

Iets soortgelijks gebeurt er voor de vragen, maar in dit geval gebruiken we een model (Vraag) om de oorspronkelijke structuur (QuestionModel) van de JSON te 'converteren' naar een handiger structuur die in de app kan worden gebruikt.

final jsonResponse = convert.jsonDecode (response.body);
eindresultaat = (jsonResponse [‘results’] als lijst)
.map ((vraag) => QuestionModel.fromJson (vraag));
vragen.waarde = resultaat
.map ((vraag) => Question.fromQuestionModel (vraag))
.ToList ();

5 - Vraagklasse

Zoals gezegd in de vorige paragraaf, gebruikt de app een andere structuur voor de vragen. In deze klasse hebben we vier eigenschappen en twee methoden:

klassenvraag {
  Vraag ({this.question, this.answers, this.correctAnswerIndex});
  fabriek Question.fromQuestionModel (QuestionModel-model) {
    laatste lijst  antwoorden = []
      ..add (model.correctAnswer)
      ..addAll (model.incorrectAnswers)
      ..shuffle ();
    uiteindelijke index = antwoorden.indexOf (model.correctAnswer);
    terugkeer Vraag (vraag: model.question, antwoorden: antwoorden, correctAnswerIndex: index);
  }
  String vraag;
  Lijst  antwoorden;
  int correctAnswerIndex;
  int gekozenAntwoordenIndex;
  bool isCorrect (String antwoord) {
    return antwoorden.indexOf (antwoord) == correctAnswerIndex;
  }
  bool isChosen (String antwoord) {
    antwoord antwoorden.indexOf (antwoord) == gekozenAntwoordenIndex;
  }
}

In de fabriek wordt de lijst met antwoorden eerst gevuld met alle antwoorden en vervolgens geschud zodat de volgorde altijd anders is. Hier krijgen we zelfs de index van het juiste antwoord, zodat we het via de constructor Question kunnen toewijzen aan correctAnswerIndex. De twee methoden worden gebruikt om te bepalen of het antwoord dat als parameter is doorgegeven, het juiste of het juiste antwoord is (ze worden in een van de volgende paragrafen beter uitgelegd).

Deel 4 - Homepage en andere schermen

1 - HomePage-widget

In de AppState ziet u een eigenschap met de naam tabController die een StreamedValue van het type AppTab (een opsomming) is, die wordt gebruikt om de pagina te streamen voor weergave in de HomePage-widget (stateless). Het werkt op deze manier: elke keer dat een andere AppTabis-set wordt geplaatst, bouwt de ValueBuilder-widget het scherm opnieuw op met de nieuwe pagina.

  • HomePage-klasse:
Widget build (BuildContext context) {
  final appState = AppStateProvider.of  (context);
  
  retour ValueBuilder (
    gestreamd: appState.tabController,
    builder: (context, snapshot) => Steiger (
      appBar: snapshot.data! = AppTab.main? null: AppBar (),
      lade: DrawerWidget (),
      body: _switchTab (snapshot.data, appState),
      )
  );
}

N.B. In dit geval wordt de appBar alleen op de hoofdpagina weergegeven.

  • _switchTab-methode:
Widget _switchTab (tabblad AppTab, AppState appState) {
  schakelaar (tabblad) {
    case AppTab.main:
      terugkeer MainPage ();
      breken;
    case AppTab.trivia:
      retourneer TriviaPage ();
      breken;
    case AppTab.samenvatting:
      terugkeer SummaryPage (statistieken: appState.triviaBloc.stats);
      breken;
    standaard:
    terugkeer MainPage ();
  }
}

2 - Instellingenpagina

Op de pagina Instellingen kunt u het aantal vragen kiezen, de moeilijkheidsgraad, de hoeveelheid tijd voor het aftellen en welk type database u wilt gebruiken. In de hoofdpagina kun je vervolgens een categorie selecteren en eindelijk het spel starten. Voor elk van deze instellingen gebruik ik een StreamedValue zodat de ValueBuilder-widget de pagina kan vernieuwen telkens wanneer een nieuwe waarde wordt ingesteld.

Deel 5 - TriviaBloc

De bedrijfslogica van de app zit in de enige BLoC met de naam TriviaBloc. Laten we deze klasse onderzoeken.

In de constructor hebben we:

TriviaBloc ({this.countdownStream, this.questions, this.tabController}) {
// Vragen krijgen van de API
  question.onChange ((data) {
    if (data.isNotEmpty) {
      laatste vragen = data..shuffle ();
     _startTrivia (vraag);
    }
  });
  countdownStream.outTransformed.listen ((data) {
     countdown = int.parse (data) * 1000;
  });
}

Hier luistert de eigenschap vragen (een StreamedList van het type Vraag) naar wijzigingen, wanneer een lijst met vragen naar de stream wordt verzonden, wordt de methode _startTrivia genoemd, waarmee het spel wordt gestart.

In plaats daarvan luistert de countdownStream alleen naar wijzigingen in de waarde van de countdown op de pagina Instellingen, zodat deze de countdown-eigenschap kan bijwerken die wordt gebruikt in de TriviaBloc-klasse.

  • _startTrivia (lijst gegevens)

Deze methode start het spel. Kortom, het reset de status van de eigenschappen, stelt de eerste vraag in die wordt getoond en roept na een seconde de methode playTrivia aan.

void _startTrivia (lijst  data) {
  index = 0;
  triviaState.value.questionIndex = 1;
  // Om de hoofdpagina en overzichtsknoppen weer te geven
  triviaState.value.isTriviaEnd = false;
  // Reset de statistieken
  stats.reset ();
  // Om de eerste vraag in te stellen (in dit geval het aftellen
  // balkanimatie start niet).
  currentQuestion.value = data.first;
  Timer (Duur (milliseconden: 1000), () {
    // Deze vlag instellen op true bij het wijzigen van de vraag
    // de animatie van de countdown-balk begint.
    triviaState.value.isTriviaPlaying = true;
  
    // Stream de eerste vraag opnieuw met de countdown-balk
    // animatie.
    currentQuestion.value = data [index];
  
    playTrivia ();
  });
}

triviaState is een StreamedValue van het type TriviaState, een klasse die wordt gebruikt om de staat van de trivia af te handelen.

class TriviaState {
  bool isTriviaPlaying = false;
  bool isTriviaEnd = false;
  bool isAnswerChosen = false;
  int questionIndex = 1;
}
  • playTrivia ()

Wanneer deze methode wordt aangeroepen, werkt een timer de timer periodiek bij en controleert of de verstreken tijd groter is dan de countdown-instelling. In dit geval wordt de timer geannuleerd, wordt de huidige vraag gemarkeerd als niet beantwoord en roept de _nextQuestionmethod een nieuwe vraag aan .

void playTrivia () {
  timer = Timer.periodic (Duur (milliseconden: refreshTime), (Timer t) {
    currentTime.value = refreshTime * t.tick;
    if (currentTime.value> countdown) {
      currentTime.value = 0;
      timer.cancel ();
      notAnswered (currentQuestion.value);
     _volgende vraag();
    }
  });
}
  • notAnswered (Vraagvraag)

Deze methode roept de methode addNoAnswer van de statsinstantie van de klasse TriviaStats aan voor elke vraag zonder antwoord, om de statistieken bij te werken.

void notAnswered (Vraagvraag) {
  stats.addNoAnswer (vraag);
}
  • _volgende vraag()

Bij deze methode wordt de index van de vragen verhoogd en als er andere vragen in de lijst staan, wordt een nieuwe vraag verzonden naar de stream currentQuestion zodat de ValueBuilder de pagina met de nieuwe vraag bijwerkt. Anders wordt de methode _endTriva genoemd, waardoor het spel wordt beëindigd.

void _nextQuestion () {
  index ++;
   if (index 
  • endTrivia ()

Hier wordt de timer geannuleerd en de vlag is TriviaEnd ingesteld op true. Na 1,5 seconden na het einde van het spel wordt de overzichtspagina weergegeven.

void _endTrivia () {
  // RESET
  timer.cancel ();
  currentTime.value = 0;
  triviaState.value.isTriviaEnd = true;
  triviaState.refresh ();
  stopTimer ();
  Timer (Duur (milliseconden: 1500), () {
     // dit wordt hier gereset om de start van de niet te activeren
     // countdown-animatie tijdens het wachten op de overzichtspagina.
     triviaState.value.isAnswerChosen = false;
     // Toon de overzichtspagina na 1,5 seconde
     tabController.value = AppTab.samenvatting;
     // Wis de laatste vraag zodat deze niet verschijnt
     // in het volgende spel
     currentQuestion.value = null;
  });
}
  • checkAnswer (Vraagvraag, String-antwoord)

Wanneer de gebruiker op een antwoord klikt, controleert deze methode of deze correct is en wordt de methode aangeroepen om een ​​positieve of negatieve score aan de statistieken toe te voegen. Daarna wordt de timer gereset en een nieuwe vraag geladen.

void checkAnswer (Vraagvraag, Antwoord op reeks) {
  if (! triviaState.value.isTriviaEnd) {
     question.chosenAnswerIndex = question.answers.indexOf (antwoord);
     if (question.isCorrect (antwoord)) {
       stats.addCorrect (vraag);
     } anders {
       stats.addWrong (vraag);
     }
     timer.cancel ();
     currentTime.value = 0;
    _volgende vraag();
  }
}
  • stopTimer ()

Wanneer deze methode wordt aangeroepen, wordt de tijd geannuleerd en wordt de vlag AntwoordenGekozen ingesteld op true om de CountdownWidget te vertellen de animatie te stoppen.

void stopTimer () {
  // Stop de timer
  timer.cancel ();
  // Door deze vlag op true te zetten, stopt de countdown-animatie
  triviaState.value.isAnswerChosen = true;
  triviaState.refresh ();
}
  • onChosenAnswer (String antwoord)

Wanneer een antwoord wordt gekozen, wordt de timer geannuleerd en wordt de index van het antwoord opgeslagen in de eigenschap AnswerAndeIndex van de antwoordenAnimation-instantie van de klasse AnswerAnimation. Deze index wordt gebruikt om dit antwoord als laatste op de stapel met widgets te plaatsen om te voorkomen dat het door alle andere antwoorden wordt gedekt.

void onChosenAnswer (String antwoord) {
  gekozenAntwoord = antwoord;
  stopTimer ();
  // Stel de gekozen Antwoorden zo in dat de antwoordwidget deze als laatste op de
  // stapel.
  
  antwoordenAnimation.value.chosenAnswerIndex =
  currentQuestion.value.answers.indexOf (antwoord);
  answersAnimation.refresh ();
}

Antwoord Animatieklasse:

klasse AnswerAnimation {
  AnswerAnimation ({this.chosenAnswerIndex, this.startPlaying});
  int gekozenAntwoordenIndex;
  bool startPlaying = false;
}
  • onChosenAnswerAnimationEnd ()

Wanneer de animatie van de antwoorden eindigt, is de vlag isAnswerChosen ingesteld op false, zodat de countdown-animatie opnieuw kan beginnen en vervolgens de methode checkAnswer wordt aangeroepen om te controleren of het antwoord correct is.

void onChosenAnwserAnimationEnd () {
  // Reset de vlag zodat de countdown-animatie kan beginnen
  triviaState.value.isAnswerChosen = false;
  triviaState.refresh ();
  checkAnswer (currentQuestion.value, gekozenAntwoord);
}
  • TriviaStats-klasse

De methoden van deze klasse worden gebruikt om de score toe te wijzen. Als de gebruiker het juiste antwoord selecteert, wordt de score met tien punten verhoogd en worden de huidige vragen toegevoegd aan de correctielijst zodat deze kunnen worden weergegeven op de overzichtspagina. Als een antwoord niet correct is, wordt de score met vier verlaagd, tenslotte als geen antwoord de score wordt met twee punten verlaagd.

class TriviaStats {
  TriviaStats () {
    corrigeert = [];
    wrongs = [];
    noAnswered = [];
    score = 0;
  }
Lijst  corrigeert;
  Lijst  fouten;
  Lijst  noAnswered;
  int score;
void addCorrect (Vraagvraag) {
    corrects.add (vraag);
    score + = 10;
  }
void addWrong (Vraagvraag) {
    wrongs.add (vraag);
    score - = 4;
  }
void addNoAnswer (Vraagvraag) {
    noAnswered.add (vraag);
    score - = 2;
  }
void reset () {
    corrigeert = [];
    wrongs = [];
    noAnswered = [];
    score = 0;
  }
}
Deel 6 - Animaties

In deze app hebben we twee soorten animaties: de geanimeerde balk onder de antwoorden geeft de resterende tijd aan om te antwoorden en de animatie die wordt afgespeeld wanneer een antwoord wordt gekozen.

1 - Animatie van de countdown-balk

Dit is een vrij eenvoudige animatie. De widget neemt als parameter de breedte van de balk, de duur en de status van het spel. De animatie start elke keer dat de widget opnieuw wordt opgebouwd en stopt als een antwoord wordt gekozen.

De oorspronkelijke kleur is groen en wordt geleidelijk rood, wat aangeeft dat de tijd bijna is afgelopen.

2 - Antwoorden-animatie

Deze animatie wordt gestart telkens wanneer een antwoord wordt gekozen. Met een eenvoudige berekening van de positie van de antwoorden, wordt elk van de antwoorden geleidelijk verplaatst naar de positie van het gekozen antwoord. Om ervoor te zorgen dat het gekozen antwoord bovenaan de stapel blijft staan, wordt dit verwisseld met het laatste element van de lijst met widgets.

// Verwissel het laatste item met de gekozen antwoord zodat het kan
// wordt weergegeven als de laatste op de stapel.
laatste laatste = widgets.last;
definitief gekozen = widgets [widget.answerAnimation.chosenAnswerIndex]; final selectedIndex = widgets.indexOf (gekozen);
widgets.last = gekozen;
widgets [gekozenIndex] = laatste;
retourcontainer (
   child: Stack (
      kinderen: widgets,
   )
);

De kleur van de vakken wordt groen als het antwoord goed is en rood als het fout is.

var newColor;
if (isCorrect) {
  newColor = Colors.green;
} anders {
  newColor = Colors.red;
}
colorAnimation = ColorTween (
  begin: answerBoxColor,
  einde: newColor,
) .Animate (controller);
wachten controller.forward ();
Deel 7 - Overzichtspagina

1 - Overzichtspagina

Deze pagina neemt als parameter een exemplaar van de TriviaStats-klasse, die de lijst met de juiste vragen, fouten en vragen zonder antwoord bevat, en bouwt een ListView met elke vraag op de juiste plaats. De huidige vraag wordt vervolgens doorgegeven aan de widget SummaryAnantwoorden die de lijst met antwoorden samenstelt.

2 - SamenvattingAntwoorden

Deze widget neemt als parameter de index van de vraag en de vraag zelf en bouwt de lijst met antwoorden op. Het juiste antwoord is groen gekleurd, terwijl als de gebruiker een onjuist antwoord heeft gekozen, dit rood is gemarkeerd en zowel de juiste als de onjuiste antwoorden toont.

Gevolgtrekking

Dit voorbeeld is verre van perfect of definitief, maar het kan een goed uitgangspunt zijn om mee te werken. Het kan bijvoorbeeld worden verbeterd door een statistiekenpagina te maken met de score van elke gespeelde game, of een gedeelte waar de gebruiker aangepaste vragen en categorieën kan maken (dit kan een goede oefening zijn om te oefenen met databases). Ik hoop dat dit nuttig kan zijn, voel je vrij om verbeteringen, suggesties of andere voor te stellen.

U kunt de broncode vinden in deze GitHub-repository.