“總統測驗”是一個關于美國前總統的問答游戲。雖然測驗的內容與總統有關,但你可以把它當作模板,來實現對任何題目的測驗。
在前幾章中,你已經了解了一些編程的基本概念。現在,準備好面對更大的挑戰吧。你會發現,無論是編程技巧,還是抽象思維,這一章都要求你有一個概念性的飛躍。特別需要強調的是,本章將使用兩個列表變量來存儲數據——應用中的問題和答案,使用索引變量來跟蹤用戶正在回答的題目。在本章結束時,對于創建測驗類應用和其他需要使用列表的應用,你已經掌握了必要的知識。

本章假設你已經熟悉了App Inventor的基礎知識:使用組件設計器構建用戶界面,用塊編輯器來定義事件處理程序并為組件添加行為。如果你還不熟悉,在繼續學習之前,請復習前面幾章。
在測驗中,用戶通過單擊“下一題”按鈕,連續地回答問題,并收到回答是否正確的反饋。
## **學習要點**
如圖8-1所示,本章覆蓋以下內容:
* 定義列表變量:用來存儲問題和答案;
* 使用索引遍歷列表,用戶每次點擊“下一題”按鈕時,顯示下一個問題;
* 使用條件語句(if)控制行為:只有在特定條件下才能執行某些操作。在用戶測驗到最后一題時,將使用if塊來處理程序;
* 每一道題對應一張不同的圖片,要實現圖片的切換。

**圖 8-1 “總統測驗”在手機中**
## **準備開始**
登陸App Inventor網站,創建新項目“PresidentsQuiz”,并設置屏幕的標題為“總統測驗”,連接測試設備。從appinventor網站下載測驗中用到的圖片:roosChurch.gif,nixon.gif,carterChina.gif和atomic.gif。在下一節中將這些圖片加載到項目中。
## **設計組件**
“總統測驗”應用的界面很簡單:顯示問題并允許用戶來回答。圖8-2顯示了應用在組件設計器中的截圖,按圖來創建組件。

**圖 8-2 組件設計器中的“總統測驗”**
首先將下載的圖片加載到項目中:單擊Media區域的Upload File按鈕,選擇一個文件(如roosChurch.gif),其他圖片也是如此。然后添加表8-1中列出的組件。
**表8-1 “總統測驗”應用所需組件**
| 組件類型 | 面板中分組 | 命名 | 作用 |
| --- | --- | --- | --- |
| Image | User Interface | Image1 | 與問題一同顯示的圖片 |
| Label | User Interface | QuestionLabel |顯示正在回答的問 |
| HorizontalArrangement| Layout| HorizontalArrangement1 |放置答案輸入框及“提交”按鈕 |
| TextBox| User Interface | AnswerText | 用戶在此輸入答案 |
| Button| User Interface | AnswerButton |用戶點擊之后提交答案 |
| Label | User Interface | RightWrongLabel |顯示“正確”或“不正確”的反饋 |
| Button| User Interface| NextButton | 用戶點擊進入下一題 |
按照下面提示設置組件屬性:
* Image1:Picture為roosChurch.gif(最先出現);Width為“Fill parent”,Height為200;
* QuestionLabel:Text為“問題…”(在塊編輯器中輸入第一個問題);
* AnswerText:Hint為“輸入回答”,Text為空,放置到HorizontalArrangement1中;
* AnswerButton:Text為“提交”,放置到HorizontalArrangement1中;
* NextButton:Text為“下一步”;
* RightWrongLabel:Text為空。
## **為組件添加行為**
編程來實現以下行為:
* 應用啟動時,顯示第一個問題以及相應的圖片;
* 點擊“下一題”按鈕時,顯示第二題,再次點擊,顯示第三題,以此類推;
* 當顯示最后一題時,點擊“下一題”按鈕將回到第一題;
* 在用戶回答問題之后,反饋回答是否正確;
首先按照表8-2的提示,定義兩個列表變量:QuestionList用來保存問題,AnswerList用來保存答案。圖8-3顯示在塊編輯器中創建的兩個列表。
**表8-2 用于保存問題和答案的列表變量**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| Initialize global QuestionList to | Variables | 保存問題的列表(更名為QuestionList) |
| Initialize global AnswerList to | Variables | 保存答案的列表(更名為AnswerList) |
| make a list | Lists | 為QuestionList插入列表項 |
| 問題內容(三個) | Text | 問題 |
| make a list | Lists | 為AnswerList插入列表項 |
| 答案內容(三個) | Text | 答案 |

**圖 8-3 問題及答案列表**
### **定義索引變量**
在整個測試過程中,每次用戶點擊“下一題”按鈕,都要跟蹤用戶正在回答的問題。定義變量currentQuestionIndex作為QuestionList和AnswerList的索引值。表8-3列出了所需的塊,圖8-4顯示了變量的定義。
**表8-3 創建索引**
| 塊的類型 | 在抽屜 | 作用 |
| --- | --- | --- |
| Initialize global currentQuestionIndex to | Variables | 保存當前問題(與答案)的索引(位置) |
| 數字1 | Math | 將currentQuestionIndex的初始值設為1(第一題) |

**圖 8-4 索引變量的初始值為1**
### **顯示第一個問題**
有了這些變量,就可以為應用設定交互行為。無論是何種應用,漸進式的開發是非常重要的,而且每一步只定義一個行為。我們首先考慮與問題相關的行為,具體而言,在應用啟動時顯示列表中的第一道題,稍后再來處理圖片的事情。
代碼塊的設定應該與列表中的具體問題無關,這樣,如果需要更換問題或創建新的測驗類應用時,只需改變列表中的具體問題,而不必修改事件處理程序。
鑒于上述考慮,對于第一道題,不要直接引用“哪位總統在大蕭條時期實施了‘新政’?”這樣的題目內容,而是引用“QuestionList的第一個插槽”這樣抽象的形式(與具體問題無關)。這樣,即使第一個插槽中的問題改變了,這些程序塊仍然有效。
select list item塊用來選擇列表中的項,使用中要求指定list(列表)及index(索引)(列表中的位置)。如果列表中有三個項,可以輸入1、2或3作為索引。
第一個行為是,在應用啟動時選擇QuestionList中的第一道題,將其寫入QuestionLabel;還記得“Android,我的車在哪兒?”的應用吧,如果想讓某件事發生在應用啟動時,可以將有關指令放在Screen1.Initialize事件處理程序中,表8-4中列出所需的塊。
**表8-4 應用啟動時加載第一個問題所需的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| Screen1.Initialize | Screen1 | 應用啟動時觸發該事件 |
| set QuestionLabel.Text to | QuestionLabel | 將第一道題內容寫入QuestionLabel |
| select list Item | Lists | 從QuestionList中選擇第一道題 |
| get Global QuestionList | Variables | 從其中選擇問題的列表 |
| 數字1 | Math | 用索引值1來選擇第一道題 |
#### **塊的作用**
應用啟動時觸發Screen1.Initialize事件。如圖8-5所示,變量QuestionList中的第一項被選中,并被寫入QuestionLabel.Text。因此,應用啟動時,用戶會看到第一道題。

**圖 8-5 應用啟動時選擇并顯示第一道題**
>  測試:連接裝有AI伴侶的設備,或點擊“connect?Emulator”打開Android模擬器。當應用啟動后,你是否看到QuestionList中的第一道題:“哪位總統在大蕭條時期實施了'新政'?”
### **遍歷所有問題**
現在為“下一題”按鈕的行為編程。之前定義的currentQuestionIndex用來記住用戶正在回答的問題,現在設定當用戶單擊“下一題”時,為currentQuestionIndex加1(即,從1變為2,或從2變為3,依此類推),并根據currentQuestionIndex的值來選擇并顯示新的問題。挑戰一下你自己,看看是否可以自己搭建這些塊。完成之后,與圖8-6進行對照。

**圖 8-6 顯示下一題**
#### **塊的作用**
第一行的塊讓變量currentQuestionIndex遞增。如果當前值為1則加到2;如果是2則加到3,以此類推。一旦currentQuestionIndex值改變,應用將以此來選擇新的問題并顯示。首次單擊“下一題”時,currentQuestionIndex從1變為2,應用將選擇并顯示QuestionList中的第二道題:“哪位總統在1979年實現中美建交?”;第二次單擊“下一題”時,currentQuestionIndex從2變為3,應用將選擇并顯示QuestionList中的第三道題:“哪位總統因水門事件而辭職?”
>  提示:花一分鐘的時間來比較一下NextButton.Click與Screen.Initialize兩個事件處理程序的差別。在Screen.Initialize中,用具體數字1來選擇列表項;而在NextButton.Click中,用索引變量currentQuestionindex來選擇列表項,即選擇第currentQuestionindex項,而非第一或第二第三項,因而點擊“下一題”將選中不同的項。這是索引最常見的用法——增加索引值來找到并顯示列表項。
問題是,索引的每次遞增,都會轉到下一題,那么當測驗到最后一題時,怎么辦呢?即:當currentQuestionIndex=3時點擊“下一題”,currentQuestionIndex將從3變為4,應用將從問題列表中選擇第currentQuestionIndex項,即第4項,而列表QuestionList中只有3項,此時Android設備將不知所措并強行退出應用。那么應用如何知道已經測驗到最后一題了呢?
>  測試:測試“下一題”按鈕,看看應用運行是否正常。在手機上按“下一題”按鈕,是否顯示第二題“哪位總統在1979年實現中美建交?”?應該是的;再按“下一題”,應該出現第三題。但如果再次點擊,就會看到錯誤提示:“Attempting to get item 4 of a list of length 3.(試圖從只有3個項的列表中獲取第4項。)”這就是程序的bug!知道原因嗎?在繼續閱讀之前試試看自己解決它。
當點擊“下一題”按鈕時,應用要問一個問題,并根據問題的答案執行不同的操作。既然已知QuestionList中包含三個問題,問題可以這樣來問:“currentQuestionIndex是否>3?”如果是,將currentQuestionIndex設回1,這樣就回到了第一道題。表8-5中列出了所需的塊。
**表8-5 檢查索引值是否到了列表的結尾所需的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| if | Control | 判斷用戶是否正在做最后一題 |
| = | Math | 檢查currentQuestionIndex的值是否為3 |
| get global currentQuestionIndex | Variables | 放入“=”左邊的插槽 |
| 數字3 | Math | 放入“=”右邊的插槽 |
| set global currentQuestionIndex to | Variables | 設為1來轉回到第一道題 |
| 數字1 | Math | 設置索引值為1 |
>  測試:單擊手機上的“下一題”按鈕,會照常出現第二題“哪位總統在1979年實現中美建交?”,繼續點擊“下一題”,將顯示第三題。下面是你真正想測的:如果再次點擊,將出現第一題(“哪位總統在大蕭條時期實施了‘新政’?”)。

**圖 8-7 檢查索引值遞增**
單擊“下一題”時,索引照舊會遞增。但程序會檢查是否currentQuestionIndex>3(問題的數量)。如果大于3,則將currentQuestionIndex重新設置為1,并顯示第一題;如果≤3,則不執行if塊內的程序,并照常顯示當前問題。

**圖 8-8 檢查測驗是否到了最后一題(第三題)**
### **讓測驗易于修改**
如果NextButton.Click中的塊能夠正常運行,恭喜你,你正在成為一名合格的程序員!但是,如果想在測驗中添加新題目(及答案),該怎么辦?這些塊還能正常運行嗎?為了驗證這一點,先在QuestionList中添加第四道題,并在AnswerList中添加第四個答案,如圖8-9。

**圖 8-9 向兩個列表中分別添加一項**
>  測試:多次單擊“下一題”按鈕,你發現無論點擊多少次,第四題始終不出現。知道問題所在嗎?在繼續閱讀之前,嘗試做些修改,以便讓第四題出現。
問題出在“最后一題”的判斷條件太具體:currentQuestionIndex>3。如果把3改為4,程序正常了,但問題是,每次增減問題和答案時,都要記著修改判斷條件。計算機程序中的這種強相關性最容易導致錯誤,特別是當程序變得復雜時。好的對策是讓程序的設計與列表中的問題數量無關。這種通用性,對于程序員來說,當你想創建其他專題的定制測驗時,可以讓程序的移植更加容易。尤其是在處理動態列表時,這樣做是必須的,例如,測驗中允許用戶添加新問題(見第10章)。一個通用性好的程序不該與3這樣的具體數字相關聯,因為這只對那些有三個問題的測驗有效。對currentQuestionIndex的判斷條件應該是QuestionList列表的長度(項數),而不是具體數字。當條件更具通用性時,即使是添加或刪除QuestionList中的項,程序也能正常運行。現在修改NextButton.Click事件處理程序,替換掉具體數字3。表8-6中列出了所需要的塊。
**表8-6 檢查列表長度所需的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| length of list| Lists | 詢問列表QuestionList中有多少個列表項 |
| get global QuestionList | Variables | 插入length of list塊的list插槽中 |
#### **塊的作用**
If塊中將currentQuestionIndex值與QuestionList的列表長度進行比較,如圖8-10所示。如果currentQuestionIndex為5,而QuestionList的長度為4,則currentQuestionIndex將被重新設置為1。值得注意的是:由于程序塊不再與3或任何具體數字相關聯,因此無論列表中有多少項,程序都將正常運行。

**圖 8-10 采取更加通用的方式檢查列表的結尾**
>  測試:當單擊“下一題”按鈕時,程序是否在四個問題間循環?在第四題后是否又回到第一題?
### **為每道題切換圖片**
現在程序已經可以遍歷所有的問題(而且代碼更加聰明靈活,也更抽象),下面來設置圖片。眼下無論顯示什么問題,圖片都是同一個,我們希望當用戶單擊“下一題”時,圖片與問題相匹配。此前在Media中載入了四張圖片,現在用圖片的文件名來創建第三個列表PictureList。然后修改NextButton.Click事件處理程序,同時切換問題與圖片。(想到currentQuestionIndex就說明你已經開竅了!)首先創建列表PictureList,用圖片文件名初始化列表,要保證列表中的文件名與先前加載的圖片文件名完全相同。圖8-11顯示了PictureList塊的樣子。

**圖 8-11 PictureList中用圖片文件名來充當列表項**
下面來修改NextButton.Click事件處理程序,以便圖片可以隨問題索引的改變而改變。Image組件的Picture屬性用于指定要顯示的圖片。表8-7中列出了修改NextButton.Click所需的塊。
**表8-7 顯示與問題相匹配的圖片所需的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| set Image1.Picture to | Image1 | 改變圖片 |
| select list Item | Lists | 選擇一個與當前問題相匹配的圖片 |
| global PictureList | Variables | 從列表中選擇一個文件名 |
| get global currentQuestionIndex | Variables | 選擇第currentQuestionIndex項|
#### **塊的作用**
rrentQuestionIndex同時充當QuestionList和PictureList兩個列表的索引,這要求正確設置各個列表,如,第一題對應第一個答案及第一張圖,第二題對應第二個答案及第二張圖,依此類推,這樣一個索引值可用于三個列表,如圖8-12所示。舉例說明:第一張圖roosChurch.gif是羅斯福總統的圖(與英國首相丘吉爾在一起),而“羅斯福”是第一個問題的答案。

**圖 8-12 每次選擇與問題匹配的第currentQuestionIndex張圖片**
>  測試:多次點擊“下一題”,每次點擊是否出現不同的圖片?
### **檢查用戶答案**
現在應用已經可以遍歷所有的試題及答案(及匹配答案的圖片),這是列表應用的極好案例。但真實的測驗要對用戶的回答判斷正誤。下面添加一些塊來告訴用戶他的回答是否正確。用戶在AnswerText中輸入答案,并點擊AnswerButton提交答案;程序用Ifelse塊將用戶輸入與標準答案作比較,并用RightWrongLabel顯示比較結果。表8-8列出了程序中用到的塊。
**表8-8 用于顯示答案是否正確的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| AnswerButton.Click | AnswerButton | 點擊AnswerButton按鈕時觸發該事件 |
| ifelse | Control | 如果回答正確,做一件事,否則做另一件事 |
| = | Math | 判斷回答是否正確 |
| AnswerText.Text | AnswerText | 包含了用戶的回答 |
| select list Item | Lists | 從AnswerList列表中選擇當前問題的答案 |
| get global AnswerList | Variables | 答案的列表 |
| get global currantQuestionIndex | Variables | 當前用戶正在回答的問題的索引值 |
| set RightWrongLabel.Text to | RightWrongLabel | 顯示回答是否正確 |
| “正確” | Text | 回答正確時顯示 |
| set RightWrongLabel.Text to | RightWrongLabel | 顯示回答是否正確 |
| “不正確” | Text | 回答錯誤時顯示 |
#### **塊的作用**
在圖8-13中,Ifelse塊用來檢驗用戶的輸入(AnswerText.Text)是否等于AnswerList中的第currentQuestionIndex項。如果currentQuestionIndex=1,程序將用戶的回答與AnswerList中的第一項“羅斯福”作對比,同樣,如果currentQuestionIndex=2,則與AnswerList中的第二項“卡特”作對比,等等。如果對比結果相同,則執行then塊,即RightWrongLabel顯示“正確!”;如果對比結果不同,執行else塊,即RightWrongLabel顯示“不正確!”。

**圖 8-13 檢查用戶的回答,并告訴用戶答案是否正確**
>  測試:嘗試回答一道題,程序會顯示你的回答是否正確。分別試驗正確和錯誤的回答。你會注意到,回答正確,意味著你的輸入必須與AnswerList中的答案完全匹配(包括大小寫、標點或空格)。繼續測試后面的問題,并確認運行正常。
應用運行正常,但你會看到,當單擊“下一題”時,雖然圖片和問題都切換到下一題,但“正確!”或“不正確!”的文本以及前一題中輸入的回答仍然顯示在屏幕上,如圖8-14所示。盡管這一點無傷大雅,但用戶肯定會發現這類的界面問題。將RightWrongLabel及AnswerText清空,需要在NextButton.Click事件處理程序中添加幾個塊,表8-9列出了所需的塊。

**圖 8-14 用戶界面上的小問題**
**表8-9 清除RightWrongLabel及AnswerText的塊**
| 塊的類型 | 所在抽屜 | 作用 |
| --- | --- | --- |
| set RightWrongLabel.Text to | RightWrongLabel | 需要清空內容的label |
| “” | Text | 當用戶點擊“下一題”時,刪除對上一題回答的反饋 |
| set AnswerText.Text to | AnswerText | 用戶對上一題的回答 |
| “” | Text | 當用戶點擊“下一題”時,刪除對上一題的回答 |
#### **塊的作用**
用戶單擊“下一題”時,圖8-15中的前兩行用于清空RightWrongLabel和AnswerText。

**圖 8-15 當轉入下一題時,清空上一題的答案及對答案的反饋**
>  測試:回答一個問題,然后點擊“提交”,再單擊Next按鈕,上一題的答案及反饋是否消失了?
## **完整的應用:總統知識測驗**
圖8-16與8-17顯示了“總統測驗”應用中塊的最終配置。

**圖 8-16 “總統測驗”應用中塊的最終配置(之一)**

**圖 8-17 “總統測驗”應用中塊的最終配置(之二)**
## **改進**
一旦測驗應用開始正常運行,你也許會樂于做一些改進,例如:
* 現在應用中只顯示與問題有關的圖片,也可以嘗試播放錄音或視頻片段。在使用聲音上,你甚至可以發展出一款“辯聲識曲(Name That Tune)”的應用;
* 本測驗對正確答案的要求過于嚴格,有幾種改進方法:一是使用text.contains塊,來檢查是用戶的輸入中是否包含了真正的答案;另一種方法是給每道題提供多個答案,通過遍歷(foreach)來檢查是否與標準答案相匹配;你還可以想辦法處理掉那些用戶輸入的多余空格,或者不做大小寫區分,等等;
* 將測驗改為多選題,這需要用另一個列表來保存每個問題的可選答案。答案也可能是一個二級列表,第二級列表中保存著特定問題的可選答案。使用ListPicker組件,讓用戶來選擇答案。更多關于lists的內容請參見第19章。
## **小結**
下面是本教程中所涉及到的概念:
* 將應用程序劃分為數據(通常保存在列表中)及事件處理程序兩個部分;使用Ifelse塊來做條件判斷,有關條件語句的更多信息,請參見第18章;
* 在事件處理程序中,程序塊只能引用抽象的名稱來指代列表項及列表長度,以便當列表數據發生變化時,程序還可以正常運行;
* 索引變量可以跟蹤當前項在列表中的位置,當索引遞增時,要小心列表的末尾,使用if塊來處理應用中的行為。
- 簡介
- 序言
- 前言
- 第 1 章 Hello 貓咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 開車不發短信
- 第 5 章 瓢蟲快跑
- 第 6 章 巴黎地圖旅游
- 第 7 章 安卓,我的車在哪?
- 第 8 章 總統測驗
- 第 9 章 木琴
- 第 10 章 出題及答題
- 第 11 章 廣播中心
- 第 12 章 遙控機器人
- 第 13 章 亞馬遜掌上書店
- 第 14 章 理解應用的結構
- 第 15 章 軟件工程與應用調試
- 第 16 章 應用中的存儲
- 第 17 章 創建動畫應用
- 第 18 章 程序中的決策:條件塊
- 第 19 章 數據列表編程
- 第 20 章 循環
- 第 21 章 定義過程
- 第 22 章 數據庫
- 第 23 章 傳感器
- 第 24 章 與Web API通信