Lepršanje: kako sastaviti kviz igru

UPDATE (01.06.2019): alternativnu verziju možete pronaći ovdje pomoću paketa za obnovu.

Uvod

U ovom bih vam članku želio pokazati kako sam stvorio ovaj primjer trivijalne igre s Flutterom i paketom frideos (pogledajte ova dva primjera da biste saznali kako to funkcionira primjer1, primjer2). To je prilično jednostavna igra, ali ona obuhvaća različite zanimljive argumente.

Aplikacija ima četiri ekrana:

  • Glavna stranica na kojoj korisnik odabire kategoriju i pokrene igru.
  • Stranica postavki na kojoj korisnik može odabrati broj pitanja, vrstu baze podataka (lokalnu ili udaljenu), vremensko ograničenje za svako pitanje i poteškoće.
  • Stranica s trivijalnim prikazom na kojoj su prikazana pitanja, rezultat, broj ispravki, pogrešaka i nije odgovoreno.
  • Stranica sa sažetkom koja prikazuje sva pitanja s točnim / pogrešnim odgovorima.

To je konačni rezultat:

Ovdje možete vidjeti bolji gif.
  • Dio 1: Postavljanje projekta
  • Dio 2: Arhitektura aplikacija
  • Dio 3: API i JSON
  • Dio 4: Početna stranica i ostali zasloni
  • 5. dio: TriviaBloc
  • Dio 6: Animacije
  • Dio 7: Stranica sažetka
  • Zaključak
Dio 1 - Postavljanje projekta

1 - Napravite novi lepršavi projekt:

lepršati stvoriti svoje ime_projekta

2 - Uredite datoteku "pubspec.yaml" i dodajte http i frideos pakete:

ovisnosti:
  vijore:
    sdk: lepršati
  http: ^ 0,12.0
  frideos: ^ 0.6.0

3- Izbrišite sadržaj datoteke main.dart

4- Stvorite strukturu projekta kao sljedeću sliku:

Pojedinosti o strukturi

  • API: ovdje će se nalaziti dart datoteke za rukovanje API-jem „Otvori bazu podataka trivia“ i podsmješni API za lokalno testiranje: api_interface.dart, mock_api.dart, trivia_api.dart.
  • Blokovi: mjesto jedinog BLoC-a aplikacije trivia_bloc.dart.
  • Modeli: appstate.dart, category.dart, models.dart, question.dart, theme.dart, trivia_stats.dart.
  • Zasloni: main_page.dart, settings_page.dart, Summary_page.dart, trivia_page.dart.
Dio 2 - Arhitektura aplikacija

U svom posljednjem članku pisao sam o različitim načinima slanja i dijeljenja podataka na više widgeta i stranica. U ovom ćemo slučaju upotrijebiti malo napredniji pristup: instanca klase Singleton nazvana appState dat će se stablu widgeta korištenjem dobavljača InheritedWidget (AppStateProvider), a to će sadržavati stanje aplikacije, neki posao logike i instancije jedinog BLoC-a koji obrađuje "dio za kviz" aplikacije. Na kraju, to će biti svojevrsna mješavina singleton-a i BLoC uzorka.

Unutar svakog widgeta moguće je dobiti instancu klase AppState pozivom:

final appState = AppStateProvider.of  (kontekst);

1 - glavni.dart

Ovo je ulazna točka aplikacije. Klasa Appis widget bez stanja gdje je proglašena instancom klase AppState i gdje se, koristeći AppStateProvider, to daje u stablo widgeta. Aplikacija AppState bit će raspoređena, zatvarajući sve tokove, u metodi raspolaganja klase AppStateProvider.

Widget MaterialApp zamotan je u ValueBuilder widget tako da, svaki put kada se odabere nova tema, cijelo stablo widgeta obnavlja, ažurirajući temu.

2 - Državno upravljanje

Kao što je prethodno rečeno, instanca appState drži stanje aplikacije. Ova klasa koristit će se za:

  • Postavke: trenutna upotrebljena tema, učitajte je / spremite pomoću SharedPreferences. Implementacija API-ja, izravnavanje ili daljinsko (korištenje API-ja s opentdb.com). Vrijeme određeno za svako pitanje.
  • Prikaz trenutne kartice: glavna stranica, trivia, sažetak.
  • Učitavanje pitanja.
  • (ako je na udaljenom API-ju) Spremite postavke kategorije, broja i poteškoće pitanja.

U konstruktoru klase:

  • _createThemes gradi teme aplikacije.
  • _loadKategorije učitavaju kategorije pitanja koja se odabiru na padajućoj stranici glavne stranice.
  • odbrojavanje je StreamedTransformiran u frideos paketu tipa , koji se iz tekstnog polja dobiva na vrijednosti za postavljanje odbrojavanja.
  • questionsAmount sadrži broj pitanja koja će se prikazati tijekom igre s trivia (zadano 5).
  • Inicijalira se instanca classTriviaBloc, koja joj prenosi tokove koji upravljaju odbrojavanjem, popisom pitanja i stranicu koja će se prikazati.
Dio 3 - API i JSON

Da bi korisnik mogao birati između lokalne i udaljene baze podataka, stvorio sam QuestionApi sučelje s dvije metode i dvije klase koje ga provode: MockApi i TriviaApi.

apstraktna klasa PitanjaAPI {
  Budućnost  getCategorije (StreamedList  kategorije);
  
  Budući  getQuestions (
    {StreamedList  pitanja,
     int broj,
     Kategorija kategorije,
     PitanjeTeškoća poteškoća,
     Vrsta pitanja});
}

Implementacija MockApi zadana je postavljena (može se mijenjati na stranici postavki aplikacije) u appState:

// API
PitanjaAPI api = MockAPI ();
konačni apiType = StreamedValue  (početni podaci: ApiType.mock);

Iako je apiTypeis samo enum za rješavanje promjena baze podataka na stranici postavki:

enum ApiType {mock, remote}

mock_api.dart:

trivia_api.dart:

1 - Izbor API-ja

Na stranici postavki korisnik može odabrati koju bazu podataka koristiti putem padajućeg izbornika:

ValueBuilder  (
  streamed: appState.apiType,
  graditelj: (kontekst, snimka) {
    vrati DropdownButton  (
      vrijednost: snapshot.data,
      onChanged: appState.setApiType,
      stavke: [
        const DropdownMenuItem  (
          vrijednost: ApiType.mock,
          dijete: Tekst ("Demo"),
        ),
        const DropdownMenuItem  (
          vrijednost: ApiType.remote,
          dijete: Tekst ('opentdb.com'),
       ),
    ]);
}),

Svaki put kad se odabere nova baza podataka, metoda setApiType promijenit će implementaciju API-ja, a kategorije će se ažurirati.

void setApiType (vrsta ApiType) {
  ako (apiType.value! = vrsta) {
    apiType.value = vrsta;
    ako (upišite == ApiType.mock) {
      api = MockAPI ();
    } else {
      api = TriviaAPI ();
    }
    _loadCategories ();
  }
}

2 - Kategorije

Da biste dobili popis kategorija koje nazivamo ovim URL-om:

https://opentdb.com/api_category.php

Izvadak odgovora:

{"trivia_categories": [{"id": 9, "name": "Opće znanje"}, {"id": 10, "name": "Zabava: Knjige"}]

Nakon dekodiranja JSON pomoću jsonDecode funkcije dart: pretvoriti biblioteku:

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

imamo ovu strukturu:

  • jsonResponse ['trivia_categories']: popis kategorija
  • jsonResponse ['trivia_categories'] [INDEX] ['id']: id kategorije
  • jsonResponse ['trivia_categories'] [INDEX] ['name']: naziv kategorije

Dakle, model će biti:

kategorija klase {
  Kategorija ({this.id, ovo ime});
  tvornička kategorija.fromJson (Karta  json) {
    povratna kategorija (id: json ['id'], ime: json ['ime']);
  }
  int id;
  Ime niza;
}

3 - Pitanja

Ako nazovemo ovaj URL:

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

to će biti odgovor:

{"response_code": 0, "results": [{"kategorija": "Zabava: Glazba", "type": "višestruko", "teškoća": "medium", "pitanje": "What French artist \ / band je poznata po sviranju na midi instrumentu "Launchpad & quot;?", "Ispravno_answer": "Madeon", "netočno_answer": ["Daft Punk", "Otkrivanje", "David Guetta"]}, {"kategorija": " Sport "," tip ":" višestruko "," težina ":" srednja "," pitanje ":" Tko je pobijedio u Državnom prvenstvu za nogometnu plej-ofu (CFP) 2015.? "," Ispravno_answer ":" Državni bakeji Ohio "," netočno_answers ": [" Alabama Crimson Tide "," Clemson Tigers "," Wisconsin Badgers "]}]}

U ovom slučaju, dekodirajući JSON, imamo ovu strukturu:

  • jsonResponse ['rezultati']: popis pitanja.
  • jsonResponse ['rezultati'] [INDEX] ['kategorija']: kategorija pitanja.
  • jsonResponse ['results'] [INDEX] ['type']: vrsta pitanja, višestruka ili logična.
  • jsonResponse ['rezultati'] [INDEX] ['pitanje']: pitanje.
  • jsonResponse ['rezultati'] [INDEX] ['ispravan_answer']: točan odgovor.
  • jsonResponse ['rezultati'] [INDEX] ['netočno_odgovori']: popis netočnih odgovora.

Model:

klase QuestionModel {
  QuestionModel ({this.question, this.correctAnswer, this.incorrectAnswers});
  tvornica QuestionModel.fromJson (Karta  json) {
    povratak QuestionModel (
      pitanje: json ['pitanje'],
      Ispravno: Odgovor: json ['Ispravni_answer'],
      netočnoAnswers: (json ['netočno_answer'] kao Popis)
        .map ((odgovor) => answer.toString ())
        .izlistati());
  }
  Gudačko pitanje;
  String ispravnoAnswer;
  Popis  netočnoAnswers;
}

4 - klasa TriviaApi

Razred implementira dvije metode sučelja QuesAApi, getCategorije i getQuestions:

  • Dobivanje kategorija

U prvom dijelu se JSON dekodira, a zatim pomoću modela, raščlanjuje dobivanje popisa kategorije tipa, na kraju se daje rezultatima kategorijama (StreamedList vrste kategorije koja se koristi za popunjavanje popisa kategorija na glavnoj stranici ).

final jsonResponse = convert.jsonDecode (response.body);
konačni rezultat = (jsonResponse ['trivia_categories'] kao Popis)
.map ((kategorija) => Kategorija.odJson (kategorija));
kategorije.value = [];
kategorije
..addAll (rezultat)
..addElement (Kategorija (id: 0, naziv: 'Bilo koja kategorija'));
  • Dobijanje pitanja

Nešto se slično događa s pitanjima, ali u ovom slučaju koristimo model (Pitanje) za "pretvaranje" izvorne strukture (QuestionModel) JSON-a u prikladniju strukturu koja se koristi u aplikaciji.

final jsonResponse = convert.jsonDecode (response.body);
konačni rezultat = (jsonResponse ['rezultati'] kao Popis)
.map ((pitanje) => QuestionModel.fromJson (pitanje));
questions.value = rezultat
.map ((pitanje) => Question.fromQuestionModel (pitanje))
.izlistati();

5 - Klasa pitanja

Kao što je rečeno u prethodnom odlomku, aplikacija koristi drugačiju strukturu za pitanja. U ovoj klasi imamo četiri svojstva i dvije metode:

klase Pitanje {
  Pitanje ({this.question, this.answers, this.correctAnswerIndex});
  tvornica Question.fromQuestionModel (Model QuestionModel) {
    finalni popis  odgovori = []
      ..add (model.correctAnswer)
      ..addAll (model.incorrectAnswers)
      ..shuffle ();
    konačni indeks = odgovori.indexOf (model.correctAnswer);
    uzvratno pitanje (pitanje: model. upit, odgovori: odgovori, tačanAnswerIndex: indeks);
  }
  Gudačko pitanje;
  Popis  odgovora;
  int correctAnswerIndex;
  int izabraniAnswerIndex;
  bool isCorno (string string) {
    uzvrati odgovori.indexOf (odgovor) == ispravanAnswerIndex;
  }
  bool isChosen (string string) {
    uzvrati odgovori.indexOf (odgovor) == izabranAnswerIndex;
  }
}

U tvornici se popis odgovora prvo popunjava sa svim odgovorima, a zatim se miješa, tako da je redoslijed uvijek drugačiji. Ovdje čak dobivamo indeks ispravnog odgovora, tako da ga možemo konstruirati u fixAnswerIndex putem konstruktora Pitanja. Dvije metode se koriste za utvrđivanje je li odgovor poslan kao parametar ispravan ili odabrani (oni će biti bolje objasnjeni u jednom od sljedećih odlomaka).

Dio 4 - Početna stranica i ostali zasloni

1 - widget HomePage

U theAppState možete vidjeti svojstvo pod nazivom tabControllerthat je StreamedValue tipa AppTab (enum), koji se koristi za strujanje stranice za prikaz u HomePage widgetu (stanje bez državljanstva). Djeluje na ovaj način: svaki put kada se drugačiji set AppTabisa, widget ValueBuilder obnovi zaslon na kojem se prikazuje nova stranica.

  • Klasa početne stranice:
Sastavljanje widgeta (kontekst BuildContext) {
  final appState = AppStateProvider.of  (kontekst);
  
  vratiti ValueBuilder (
    streamed: appState.tabController,
    graditelj: (kontekst, snimka) => Skele (
      appBar: snapshot.data! = AppTab.main? null: AppBar (),
      ladica: DrawerWidget (),
      tijelo: _switchTab (snapshot.data, appState),
      ),
  );
}

N.B. U tom će slučaju appBar biti prikazan samo na glavnoj stranici.

  • _switchTab metoda:
Widget _switchTab (kartica AppTab, appState appState) {
  preklopnik (tab) {
    slučaj AppTab.main:
      vratiti MainPage ();
      pauza;
    slučaj AppTab.trivia:
      vratiti TriviaPage ();
      pauza;
    slučaj AppTab.summary:
      vratiti SummaryPage (statistika: appState.triviaBloc.stats);
      pauza;
    zadano:
    vratiti MainPage ();
  }
}

2 - Stranica postavki

Na stranici Postavke možete odabrati broj pitanja koja će se prikazivati, poteškoće, količinu vremena za odbrojavanje i vrstu baze podataka koju želite koristiti. Na glavnoj stranici možete odabrati kategoriju i konačno započeti igru. Za svaku od ovih postavki koristim StreamedValue kako bi widget ValueBuilder mogao osvježiti stranicu svaki put kada se postavi nova vrijednost.

5. dio - TriviaBloc

Poslovna logika aplikacije nalazi se u jedinom BLoC-u koji se zove TriviaBloc. Ispitajmo ovu klasu.

U konstruktoru imamo:

TriviaBloc ({this.countdownStream, this.questions, this.tabController}) {
// Dobivanje pitanja iz API-ja
  questions.on Change ((podaci) {
    ako (data.isNotEmpty) {
      završna pitanja = podaci..shuffle ();
     _startTrivia (pitanja);
    }
  });
  countdownStream.outTransformed.listen ((podaci) {
     odbrojavanje = int.parse (podaci) * 1000;
  });
}

Ovdje svojstvo pitanja (StreamedList of Type Question) sluša promjene, kada se popis pitanja šalje streamu, poziva se metoda _startTrivia, započinjući igru.

Umjesto toga, countdownStream samo sluša promjene vrijednosti odbrojavanja na stranici Postavke kako bi mogao ažurirati svojstvo odbrojavanja koje se koristi u klasi TriviaBloc.

  • _startTrivia (Popis

Ova metoda započinje igru. U osnovi resetira stanje svojstava, postavlja prvo pitanje i nakon jedne sekunde poziva metodu playTrivia.

void _startTrivia (Popis )
  indeks = 0;
  triviaState.value.questionIndex = 1;
  // Prikazivanje gumba glavne stranice i sažetka
  triviaState.value.isTriviaEnd = netočno;
  // Poništi statistiku
  stats.reset ();
  // Da biste postavili početno pitanje (u ovom slučaju odbrojavanje)
  // animacija trake neće se pokrenuti).
  currentQuestion.value = data.first;
  Mjerač vremena (trajanje (milisekunde: 1000), () {
    // Postavljanje ove zastave točno na promjenu pitanja
    // Počinje animacija trake odbrojavanja.
    triviaState.value.isTriviaPlaying = istina;
  
    // Prvo pitanje postavite ponovo sa trakom odbrojavanja
    // animacija.
    currentQuestion.value = podaci [indeks];
  
    playTrivia ();
  });
}

triviaState je StreamedValue tipa TriviaState, klasa koja se koristi za obradu stanja trivia.

klasa TriviaState {
  bool isTriviaPlaying = netočno;
  bool jeTriviaEnd = netočno;
  bool isAnswerChosen = netočno;
  int pitanjeIndex = 1;
}
  • playTrivia ()

Kada se pozove ova metoda, mjerač periodično ažurira tajmer i provjerava je li prođeno vrijeme veće od postavke odbrojavanja, u ovom slučaju otkazuje tajmer, označava trenutno pitanje kao neodgovoreno i poziva _nextQuestionmethod da pokaže novo pitanje ,

void playTrivia () {
  timer = Timer.periodic (Trajanje (milisekunde: refreshTime), (Timer t) {
    currentTime.value = refreshTime * t.tick;
    ako (currentTime.value> odbrojavanje) {
      currentTime.value = 0;
      timer.cancel ();
      notAnswered (currentQuestion.value);
     _sljedeće pitanje();
    }
  });
}
  • notAnswered (Pitanje pitanje)

Ova metoda poziva metodu addNoAnswer statističke instance za klasu TriviaStats za svako pitanje bez odgovora kako bi se ažurirala statistika.

void notAnswered (Pitanje pitanje) {
  stats.addNoAnswer (pitanje);
}
  • _sljedeće pitanje()

U ovoj se metodi povećava indeks pitanja, a ako postoje druga pitanja na popisu, tada se novo pitanje šalje u stream currentQuestion, tako da ValueBuilder stranicu ažurira novim pitanjem. Inače se zove _endTriva metoda, koja završava igru.

void _nextQuestion () {
  Indeks ++;
   ako (indeks 
  • endTrivia ()

Ovdje se tajmer otkazuje i zastava jeTriviaEnd postavljena na true. Nakon 1,5 sekunde nakon završetka igre, prikazuje se stranica sa sažetkom.

void _endTrivia () {
  // RESET
  timer.cancel ();
  currentTime.value = 0;
  triviaState.value.isTriviaEnd = istina;
  triviaState.refresh ();
  stopTimer ();
  Mjerač vremena (trajanje (milisekunde: 1500), () {
     // ovo se resetira ovdje da ne pokrene početak
     // animacija odbrojavanja dok čekate stranicu sa sažetkom.
     triviaState.value.isAnswerChosen = netočno;
     // Prikaži stranicu sažetka nakon 1,5 s
     tabController.value = AppTab.summary;
     // Obrišite posljednje pitanje tako da se ne pojavi
     // u sljedećoj igri
     currentQuestion.value = nula;
  });
}
  • checkAnswer (Pitanje pitanje, string string)

Kad korisnik klikne na odgovor, ova metoda provjerava je li točna i zove metodu kako bi se statistikama dodao pozitivan ili negativan rezultat. Tada se timer resetira i učitava se novo pitanje.

void checkAnswer (Pitanje pitanje, String answer) {
  if (! triviaState.value.isTriviaEnd) {
     pitanje.chosenAnswerIndex = pitanje.answers.indexOf (odgovor);
     ako (pitanje.ispravno (odgovor)) {
       stats.addCorrect (pitanje);
     } else {
       stats.addWrong (pitanje);
     }
     timer.cancel ();
     currentTime.value = 0;
    _sljedeće pitanje();
  }
}
  • stopTimer ()

Kada se pozove ova metoda, vrijeme se poništava i zastava jeAnswerChosen postavljena na true da bi rekla CountdownWidget da zaustavi animaciju.

void stopTimer () {
  // Zaustavite tajmer
  timer.cancel ();
  // Ako je ova zastava postavljena na istinito, animacija odbrojavanja će se zaustaviti
  triviaState.value.isAnswerChosen = istina;
  triviaState.refresh ();
}
  • onChosenAnswer (nizovi odgovora)

Kada je odabran odgovor, odbrojavanje vremena se poništava i indeks odgovora sprema se u svojstvo odabranogAnswerIndex u instanci answerAnimation klase AnswerAnimation. Ovaj indeks koristi se za postavljanje ovog odgovora na snop widgeta kako bi se izbjeglo da je pokriveno svim ostalim odgovorima.

void onChosenAnswer (string string) {
  izabranAnswer = odgovor;
  stopTimer ();
  // Postavite izabrani odgovor tako da ga widget za odgovor može staviti zadnji na
  // stog.
  
  odgovoriAnimation.value.chosenAnswerIndex =
  currentQuestion.value.answers.indexOf (odgovor);
  answersAnimation.refresh ();
}

AnswerAnimation class:

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

Kada animacija odgovora završi, zastava isAnswerChosen je postavljena na lažno, kako bi se omogućilo da se animacija odbrojavanja može ponovo pokrenuti, a zatim poziva metodu checkAnswer da provjeri je li odgovor točan.

void onChosenAnwserAnimationEnd () {
  // Ponovno postavite zastavicu tako da se može pokrenuti animacija odbrojavanja
  triviaState.value.isAnswerChosen = netočno;
  triviaState.refresh ();
  checkAnswer (currentQuestion.value, selectedAnswer);
}
  • TriviaStats klasa

Metode ove klase koriste se za dodjeljivanje bodova. Ako korisnik odabere ispravan odgovor, rezultat se povećava za deset bodova, a trenutna pitanja dodaju na popis ispravki, tako da se mogu prikazati na stranici sažetka, ako je odgovor nije točan, onda se rezultat smanjuje za četiri, na kraju ako bez odgovora rezultat se smanjuje za dva boda.

klasa TriviaStats {
  TriviaStats () {
    ispravlja = [];
    pogriješi = [];
    noAnswered = [];
    rezultat = 0;
  }
Popis  ispravlja;
  Nabrojite  krive;
  Popis  bezAnswered;
  int rezultat;
void addCorrect (Pitanje pitanje) {
    corrects.add (pitanje);
    rezultat + = 10;
  }
void addWrong (Pitanje pitanje) {
    wrongs.add (pitanje);
    rezultat - = 4;
  }
void addNoAnswer (Pitanje pitanje) {
    noAnswered.add (pitanje);
    rezultat - = 2;
  }
nevažeće resetiranje () {
    ispravlja = [];
    pogriješi = [];
    noAnswered = [];
    rezultat = 0;
  }
}
Dio 6 - Animacije

U ovoj aplikaciji imamo dvije vrste animacija: animirana traka ispod odgovora označava preostalo vrijeme za odgovor i animacija koja se igra kad je odgovor odabran.

1 - animacija trake odbrojavanja

Ovo je prilično jednostavna animacija. Widget uzima kao parametar širinu trake, trajanje i stanje igre. Animacija se pokreće svaki put kada se widget obnovi i zaustavi ako se odabere odgovor.

Početna boja je zelena i postupno se pretvara u crvenu, signalizirajući da je vrijeme gotovo.

2 - Odgovori na animaciju

Animacija se pokreće svaki put kad se odabere odgovor. Jednostavnim izračunavanjem položaja odgovora svaki se od njih progresivno premješta u poziciju odabranog odgovora. Kako bi odabrani odgovor ostao na vrhu snopa, to je zamijenjeno posljednjim elementom popisa widgeta.

// Zamijenite zadnju stavku s odabranom nagradom da bi mogla
// biti prikazan kao posljednji na hrpi.
final last = widgets.last;
konačni izabrani = widgeti [widget.answerAnimation.chosenAnswerIndex]; konačni izabraniIndex = widgets.indexOf (izabran);
widgets.last = izabrano;
widgeti [selectedIndex] = zadnji;
povratni spremnik (
   dijete: Stack (
      djeca: widgeti,
   ),
);

Boja kutija postaje zelena ako je odgovor točan, a crvena ako je pogrešna.

var newColor;
ako je (ispravan) {
  newColor = Colors.green;
} else {
  newColor = Boje.red;
}
colorAnimation = ColorTween (
  započeti: answerBoxColor,
  kraj: newColor,
) .Animate (kontroler);
čekajte controller.forward ();
Dio 7 - stranica sažetka

1 - Stranica sažetka

Ova stranica uzima kao parametar instancu klase TriviaStats, koja sadrži popis ispravljenih pitanja, pogrešaka i onih bez odabranog odgovora, i gradi ListView koji prikazuje svako pitanje na pravom mjestu. Trenutno se pitanje prosljeđuje u widget SummaryAnswers koji sastavlja popis odgovora.

2 - Sažetak Odgovori

Ovaj widget uzima kao parametar indeks pitanja i samo pitanje i sastavlja popis odgovora. Ispravan odgovor obojen je zelenom bojom, a ako je korisnik izabrao netočan odgovor, ovaj je označen crvenom bojom, prikazujući i ispravne i netočne odgovore.

Zaključak

Ovaj je primjer daleko savršen ili konačan, ali može biti dobro polazište za suradnju. Na primjer, može se poboljšati stvaranjem stranice s statistikama s rezultatima svake odigrane igre ili odjeljkom u kojem korisnik može kreirati prilagođena pitanja i kategorije (to može biti odlična vježba za vježbanje s bazama podataka). Nadam se da ovo može biti korisno, slobodno predložite poboljšanja, prijedloge ili drugo.

Izvorni kôd možete pronaći u ovom GitHub skladištu.