“總統(tǒng)測驗(yàn)”是一個(gè)關(guān)于美國前總統(tǒng)的問答游戲。雖然測驗(yàn)的內(nèi)容與總統(tǒng)有關(guān),但你可以把它當(dāng)作模板,來實(shí)現(xiàn)對(duì)任何題目的測驗(yàn)。
在前幾章中,你已經(jīng)了解了一些編程的基本概念?,F(xiàn)在,準(zhǔn)備好面對(duì)更大的挑戰(zhàn)吧。你會(huì)發(fā)現(xiàn),無論是編程技巧,還是抽象思維,這一章都要求你有一個(gè)概念性的飛躍。特別需要強(qiáng)調(diào)的是,本章將使用兩個(gè)列表變量來存儲(chǔ)數(shù)據(jù)——應(yīng)用中的問題和答案,使用索引變量來跟蹤用戶正在回答的題目。在本章結(jié)束時(shí),對(duì)于創(chuàng)建測驗(yàn)類應(yīng)用和其他需要使用列表的應(yīng)用,你已經(jīng)掌握了必要的知識(shí)。
本章假設(shè)你已經(jīng)熟悉了App Inventor的基礎(chǔ)知識(shí):使用組件設(shè)計(jì)器構(gòu)建用戶界面,用塊編輯器來定義事件處理程序并為組件添加行為。如果你還不熟悉,在繼續(xù)學(xué)習(xí)之前,請(qǐng)復(fù)習(xí)前面幾章。
在測驗(yàn)中,用戶通過單擊“下一題”按鈕,連續(xù)地回答問題,并收到回答是否正確的反饋。
如圖8-1所示,本章覆蓋以下內(nèi)容:
定義列表變量:用來存儲(chǔ)問題和答案;
使用索引遍歷列表,用戶每次點(diǎn)擊“下一題”按鈕時(shí),顯示下一個(gè)問題;
使用條件語句(if)控制行為:只有在特定條件下才能執(zhí)行某些操作。在用戶測驗(yàn)到最后一題時(shí),將使用if塊來處理程序;
圖 8-1 “總統(tǒng)測驗(yàn)”在手機(jī)中
登陸App Inventor網(wǎng)站,創(chuàng)建新項(xiàng)目“PresidentsQuiz”,并設(shè)置屏幕的標(biāo)題為“總統(tǒng)測驗(yàn)”,連接測試設(shè)備。從appinventor網(wǎng)站下載測驗(yàn)中用到的圖片:roosChurch.gif,nixon.gif,carterChina.gif和atomic.gif。在下一節(jié)中將這些圖片加載到項(xiàng)目中。
“總統(tǒng)測驗(yàn)”應(yīng)用的界面很簡單:顯示問題并允許用戶來回答。圖8-2顯示了應(yīng)用在組件設(shè)計(jì)器中的截圖,按圖來創(chuàng)建組件。
圖 8-2 組件設(shè)計(jì)器中的“總統(tǒng)測驗(yàn)”
首先將下載的圖片加載到項(xiàng)目中:單擊Media區(qū)域的Upload File按鈕,選擇一個(gè)文件(如roosChurch.gif),其他圖片也是如此。然后添加表8-1中列出的組件。
表8-1 “總統(tǒng)測驗(yàn)”應(yīng)用所需組件
組件類型 | 面板中分組 | 命名 | 作用 |
---|---|---|---|
Image | User Interface | Image1 | 與問題一同顯示的圖片 |
Label | User Interface | QuestionLabel | 顯示正在回答的問 |
HorizontalArrangement | Layout | HorizontalArrangement1 | 放置答案輸入框及“提交”按鈕 |
TextBox | User Interface | AnswerText | 用戶在此輸入答案 |
Button | User Interface | AnswerButton | 用戶點(diǎn)擊之后提交答案 |
Label | User Interface | RightWrongLabel | 顯示“正確”或“不正確”的反饋 |
Button | User Interface | NextButton | 用戶點(diǎn)擊進(jìn)入下一題 |
按照下面提示設(shè)置組件屬性:
Image1:Picture為roosChurch.gif(最先出現(xiàn));Width為“Fill parent”,Height為200;
QuestionLabel:Text為“問題…”(在塊編輯器中輸入第一個(gè)問題);
AnswerText:Hint為“輸入回答”,Text為空,放置到HorizontalArrangement1中;
AnswerButton:Text為“提交”,放置到HorizontalArrangement1中;
NextButton:Text為“下一步”;
編程來實(shí)現(xiàn)以下行為:
應(yīng)用啟動(dòng)時(shí),顯示第一個(gè)問題以及相應(yīng)的圖片;
點(diǎn)擊“下一題”按鈕時(shí),顯示第二題,再次點(diǎn)擊,顯示第三題,以此類推;
當(dāng)顯示最后一題時(shí),點(diǎn)擊“下一題”按鈕將回到第一題;
首先按照表8-2的提示,定義兩個(gè)列表變量:QuestionList用來保存問題,AnswerList用來保存答案。圖8-3顯示在塊編輯器中創(chuàng)建的兩個(gè)列表。
表8-2 用于保存問題和答案的列表變量
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
Initialize global QuestionList to | Variables | 保存問題的列表(更名為QuestionList) |
Initialize global AnswerList to | Variables | 保存答案的列表(更名為AnswerList) |
make a list | Lists | 為QuestionList插入列表項(xiàng) |
問題內(nèi)容(三個(gè)) | Text | 問題 |
make a list | Lists | 為AnswerList插入列表項(xiàng) |
答案內(nèi)容(三個(gè)) | Text | 答案 |
圖 8-3 問題及答案列表
在整個(gè)測試過程中,每次用戶點(diǎn)擊“下一題”按鈕,都要跟蹤用戶正在回答的問題。定義變量currentQuestionIndex作為QuestionList和AnswerList的索引值。表8-3列出了所需的塊,圖8-4顯示了變量的定義。
表8-3 創(chuàng)建索引
塊的類型 | 在抽屜 | 作用 |
---|---|---|
Initialize global currentQuestionIndex to | Variables | 保存當(dāng)前問題(與答案)的索引(位置) |
數(shù)字1 | Math | 將currentQuestionIndex的初始值設(shè)為1(第一題) |
圖 8-4 索引變量的初始值為1
有了這些變量,就可以為應(yīng)用設(shè)定交互行為。無論是何種應(yīng)用,漸進(jìn)式的開發(fā)是非常重要的,而且每一步只定義一個(gè)行為。我們首先考慮與問題相關(guān)的行為,具體而言,在應(yīng)用啟動(dòng)時(shí)顯示列表中的第一道題,稍后再來處理圖片的事情。
代碼塊的設(shè)定應(yīng)該與列表中的具體問題無關(guān),這樣,如果需要更換問題或創(chuàng)建新的測驗(yàn)類應(yīng)用時(shí),只需改變列表中的具體問題,而不必修改事件處理程序。
鑒于上述考慮,對(duì)于第一道題,不要直接引用“哪位總統(tǒng)在大蕭條時(shí)期實(shí)施了‘新政’?”這樣的題目內(nèi)容,而是引用“QuestionList的第一個(gè)插槽”這樣抽象的形式(與具體問題無關(guān))。這樣,即使第一個(gè)插槽中的問題改變了,這些程序塊仍然有效。
select list item塊用來選擇列表中的項(xiàng),使用中要求指定list(列表)及index(索引)(列表中的位置)。如果列表中有三個(gè)項(xiàng),可以輸入1、2或3作為索引。
第一個(gè)行為是,在應(yīng)用啟動(dòng)時(shí)選擇QuestionList中的第一道題,將其寫入QuestionLabel;還記得“Android,我的車在哪兒?”的應(yīng)用吧,如果想讓某件事發(fā)生在應(yīng)用啟動(dòng)時(shí),可以將有關(guān)指令放在Screen1.Initialize事件處理程序中,表8-4中列出所需的塊。
表8-4 應(yīng)用啟動(dòng)時(shí)加載第一個(gè)問題所需的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
Screen1.Initialize | Screen1 | 應(yīng)用啟動(dòng)時(shí)觸發(fā)該事件 |
set QuestionLabel.Text to | QuestionLabel | 將第一道題內(nèi)容寫入QuestionLabel |
select list Item | Lists | 從QuestionList中選擇第一道題 |
get Global QuestionList | Variables | 從其中選擇問題的列表 |
數(shù)字1 | Math | 用索引值1來選擇第一道題 |
應(yīng)用啟動(dòng)時(shí)觸發(fā)Screen1.Initialize事件。如圖8-5所示,變量QuestionList中的第一項(xiàng)被選中,并被寫入QuestionLabel.Text。因此,應(yīng)用啟動(dòng)時(shí),用戶會(huì)看到第一道題。
圖 8-5 應(yīng)用啟動(dòng)時(shí)選擇并顯示第一道題
測試:連接裝有AI伴侶的設(shè)備,或點(diǎn)擊“connect?Emulator”打開Android模擬器。當(dāng)應(yīng)用啟動(dòng)后,你是否看到QuestionList中的第一道題:“哪位總統(tǒng)在大蕭條時(shí)期實(shí)施了'新政'?”
現(xiàn)在為“下一題”按鈕的行為編程。之前定義的currentQuestionIndex用來記住用戶正在回答的問題,現(xiàn)在設(shè)定當(dāng)用戶單擊“下一題”時(shí),為currentQuestionIndex加1(即,從1變?yōu)?,或從2變?yōu)?,依此類推),并根據(jù)currentQuestionIndex的值來選擇并顯示新的問題。挑戰(zhàn)一下你自己,看看是否可以自己搭建這些塊。完成之后,與圖8-6進(jìn)行對(duì)照。
圖 8-6 顯示下一題
第一行的塊讓變量currentQuestionIndex遞增。如果當(dāng)前值為1則加到2;如果是2則加到3,以此類推。一旦currentQuestionIndex值改變,應(yīng)用將以此來選擇新的問題并顯示。首次單擊“下一題”時(shí),currentQuestionIndex從1變?yōu)?,應(yīng)用將選擇并顯示QuestionList中的第二道題:“哪位總統(tǒng)在1979年實(shí)現(xiàn)中美建交?”;第二次單擊“下一題”時(shí),currentQuestionIndex從2變?yōu)?,應(yīng)用將選擇并顯示QuestionList中的第三道題:“哪位總統(tǒng)因水門事件而辭職?”
測試:測試“下一題”按鈕,看看應(yīng)用運(yùn)行是否正常。在手機(jī)上按“下一題”按鈕,是否顯示第二題“哪位總統(tǒng)在1979年實(shí)現(xiàn)中美建交?”?應(yīng)該是的;再按“下一題”,應(yīng)該出現(xiàn)第三題。但如果再次點(diǎn)擊,就會(huì)看到錯(cuò)誤提示:“Attempting to get item 4 of a list of length 3.(試圖從只有3個(gè)項(xiàng)的列表中獲取第4項(xiàng)。)”這就是程序的bug!知道原因嗎?在繼續(xù)閱讀之前試試看自己解決它。
當(dāng)點(diǎn)擊“下一題”按鈕時(shí),應(yīng)用要問一個(gè)問題,并根據(jù)問題的答案執(zhí)行不同的操作。既然已知QuestionList中包含三個(gè)問題,問題可以這樣來問:“currentQuestionIndex是否>3?”如果是,將currentQuestionIndex設(shè)回1,這樣就回到了第一道題。表8-5中列出了所需的塊。
表8-5 檢查索引值是否到了列表的結(jié)尾所需的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
if | Control | 判斷用戶是否正在做最后一題 |
= | Math | 檢查currentQuestionIndex的值是否為3 |
get global currentQuestionIndex | Variables | 放入“=”左邊的插槽 |
數(shù)字3 | Math | 放入“=”右邊的插槽 |
set global currentQuestionIndex to | Variables | 設(shè)為1來轉(zhuǎn)回到第一道題 |
數(shù)字1 | Math | 設(shè)置索引值為1 |
測試:單擊手機(jī)上的“下一題”按鈕,會(huì)照常出現(xiàn)第二題“哪位總統(tǒng)在1979年實(shí)現(xiàn)中美建交?”,繼續(xù)點(diǎn)擊“下一題”,將顯示第三題。下面是你真正想測的:如果再次點(diǎn)擊,將出現(xiàn)第一題(“哪位總統(tǒng)在大蕭條時(shí)期實(shí)施了‘新政’?”)。
圖 8-7 檢查索引值遞增
單擊“下一題”時(shí),索引照舊會(huì)遞增。但程序會(huì)檢查是否currentQuestionIndex>3(問題的數(shù)量)。如果大于3,則將currentQuestionIndex重新設(shè)置為1,并顯示第一題;如果≤3,則不執(zhí)行if塊內(nèi)的程序,并照常顯示當(dāng)前問題。
圖 8-8 檢查測驗(yàn)是否到了最后一題(第三題)
如果NextButton.Click中的塊能夠正常運(yùn)行,恭喜你,你正在成為一名合格的程序員!但是,如果想在測驗(yàn)中添加新題目(及答案),該怎么辦?這些塊還能正常運(yùn)行嗎?為了驗(yàn)證這一點(diǎn),先在QuestionList中添加第四道題,并在AnswerList中添加第四個(gè)答案,如圖8-9。
圖 8-9 向兩個(gè)列表中分別添加一項(xiàng)
測試:多次單擊“下一題”按鈕,你發(fā)現(xiàn)無論點(diǎn)擊多少次,第四題始終不出現(xiàn)。知道問題所在嗎?在繼續(xù)閱讀之前,嘗試做些修改,以便讓第四題出現(xiàn)。
問題出在“最后一題”的判斷條件太具體:currentQuestionIndex>3。如果把3改為4,程序正常了,但問題是,每次增減問題和答案時(shí),都要記著修改判斷條件。計(jì)算機(jī)程序中的這種強(qiáng)相關(guān)性最容易導(dǎo)致錯(cuò)誤,特別是當(dāng)程序變得復(fù)雜時(shí)。好的對(duì)策是讓程序的設(shè)計(jì)與列表中的問題數(shù)量無關(guān)。這種通用性,對(duì)于程序員來說,當(dāng)你想創(chuàng)建其他專題的定制測驗(yàn)時(shí),可以讓程序的移植更加容易。尤其是在處理動(dòng)態(tài)列表時(shí),這樣做是必須的,例如,測驗(yàn)中允許用戶添加新問題(見第10章)。一個(gè)通用性好的程序不該與3這樣的具體數(shù)字相關(guān)聯(lián),因?yàn)檫@只對(duì)那些有三個(gè)問題的測驗(yàn)有效。對(duì)currentQuestionIndex的判斷條件應(yīng)該是QuestionList列表的長度(項(xiàng)數(shù)),而不是具體數(shù)字。當(dāng)條件更具通用性時(shí),即使是添加或刪除QuestionList中的項(xiàng),程序也能正常運(yùn)行?,F(xiàn)在修改NextButton.Click事件處理程序,替換掉具體數(shù)字3。表8-6中列出了所需要的塊。
表8-6 檢查列表長度所需的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
length of list | Lists | 詢問列表QuestionList中有多少個(gè)列表項(xiàng) |
get global QuestionList | Variables | 插入length of list塊的list插槽中 |
If塊中將currentQuestionIndex值與QuestionList的列表長度進(jìn)行比較,如圖8-10所示。如果currentQuestionIndex為5,而QuestionList的長度為4,則currentQuestionIndex將被重新設(shè)置為1。值得注意的是:由于程序塊不再與3或任何具體數(shù)字相關(guān)聯(lián),因此無論列表中有多少項(xiàng),程序都將正常運(yùn)行。
圖 8-10 采取更加通用的方式檢查列表的結(jié)尾
測試:當(dāng)單擊“下一題”按鈕時(shí),程序是否在四個(gè)問題間循環(huán)?在第四題后是否又回到第一題?
現(xiàn)在程序已經(jīng)可以遍歷所有的問題(而且代碼更加聰明靈活,也更抽象),下面來設(shè)置圖片。眼下無論顯示什么問題,圖片都是同一個(gè),我們希望當(dāng)用戶單擊“下一題”時(shí),圖片與問題相匹配。此前在Media中載入了四張圖片,現(xiàn)在用圖片的文件名來創(chuàng)建第三個(gè)列表PictureList。然后修改NextButton.Click事件處理程序,同時(shí)切換問題與圖片。(想到currentQuestionIndex就說明你已經(jīng)開竅了?。┦紫葎?chuàng)建列表PictureList,用圖片文件名初始化列表,要保證列表中的文件名與先前加載的圖片文件名完全相同。圖8-11顯示了PictureList塊的樣子。
圖 8-11 PictureList中用圖片文件名來充當(dāng)列表項(xiàng)
下面來修改NextButton.Click事件處理程序,以便圖片可以隨問題索引的改變而改變。Image組件的Picture屬性用于指定要顯示的圖片。表8-7中列出了修改NextButton.Click所需的塊。
表8-7 顯示與問題相匹配的圖片所需的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
set Image1.Picture to | Image1 | 改變圖片 |
select list Item | Lists | 選擇一個(gè)與當(dāng)前問題相匹配的圖片 |
global PictureList | Variables | 從列表中選擇一個(gè)文件名 |
get global currentQuestionIndex | Variables | 選擇第currentQuestionIndex項(xiàng) |
rrentQuestionIndex同時(shí)充當(dāng)QuestionList和PictureList兩個(gè)列表的索引,這要求正確設(shè)置各個(gè)列表,如,第一題對(duì)應(yīng)第一個(gè)答案及第一張圖,第二題對(duì)應(yīng)第二個(gè)答案及第二張圖,依此類推,這樣一個(gè)索引值可用于三個(gè)列表,如圖8-12所示。舉例說明:第一張圖roosChurch.gif是羅斯福總統(tǒng)的圖(與英國首相丘吉爾在一起),而“羅斯?!笔堑谝粋€(gè)問題的答案。
圖 8-12 每次選擇與問題匹配的第currentQuestionIndex張圖片
測試:多次點(diǎn)擊“下一題”,每次點(diǎn)擊是否出現(xiàn)不同的圖片?
現(xiàn)在應(yīng)用已經(jīng)可以遍歷所有的試題及答案(及匹配答案的圖片),這是列表應(yīng)用的極好案例。但真實(shí)的測驗(yàn)要對(duì)用戶的回答判斷正誤。下面添加一些塊來告訴用戶他的回答是否正確。用戶在AnswerText中輸入答案,并點(diǎn)擊AnswerButton提交答案;程序用Ifelse塊將用戶輸入與標(biāo)準(zhǔn)答案作比較,并用RightWrongLabel顯示比較結(jié)果。表8-8列出了程序中用到的塊。
表8-8 用于顯示答案是否正確的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
AnswerButton.Click | AnswerButton | 點(diǎn)擊AnswerButton按鈕時(shí)觸發(fā)該事件 |
ifelse | Control | 如果回答正確,做一件事,否則做另一件事 |
= | Math | 判斷回答是否正確 |
AnswerText.Text | AnswerText | 包含了用戶的回答 |
select list Item | Lists | 從AnswerList列表中選擇當(dāng)前問題的答案 |
get global AnswerList | Variables | 答案的列表 |
get global currantQuestionIndex | Variables | 當(dāng)前用戶正在回答的問題的索引值 |
set RightWrongLabel.Text to | RightWrongLabel | 顯示回答是否正確 |
“正確” | Text | 回答正確時(shí)顯示 |
set RightWrongLabel.Text to | RightWrongLabel | 顯示回答是否正確 |
“不正確” | Text | 回答錯(cuò)誤時(shí)顯示 |
在圖8-13中,Ifelse塊用來檢驗(yàn)用戶的輸入(AnswerText.Text)是否等于AnswerList中的第currentQuestionIndex項(xiàng)。如果currentQuestionIndex=1,程序?qū)⒂脩舻幕卮鹋cAnswerList中的第一項(xiàng)“羅斯?!弊鲗?duì)比,同樣,如果currentQuestionIndex=2,則與AnswerList中的第二項(xiàng)“卡特”作對(duì)比,等等。如果對(duì)比結(jié)果相同,則執(zhí)行then塊,即RightWrongLabel顯示“正確!”;如果對(duì)比結(jié)果不同,執(zhí)行else塊,即RightWrongLabel顯示“不正確!”。
圖 8-13 檢查用戶的回答,并告訴用戶答案是否正確
測試:嘗試回答一道題,程序會(huì)顯示你的回答是否正確。分別試驗(yàn)正確和錯(cuò)誤的回答。你會(huì)注意到,回答正確,意味著你的輸入必須與AnswerList中的答案完全匹配(包括大小寫、標(biāo)點(diǎn)或空格)。繼續(xù)測試后面的問題,并確認(rèn)運(yùn)行正常。
應(yīng)用運(yùn)行正常,但你會(huì)看到,當(dāng)單擊“下一題”時(shí),雖然圖片和問題都切換到下一題,但“正確!”或“不正確!”的文本以及前一題中輸入的回答仍然顯示在屏幕上,如圖8-14所示。盡管這一點(diǎn)無傷大雅,但用戶肯定會(huì)發(fā)現(xiàn)這類的界面問題。將RightWrongLabel及AnswerText清空,需要在NextButton.Click事件處理程序中添加幾個(gè)塊,表8-9列出了所需的塊。
圖 8-14 用戶界面上的小問題
表8-9 清除RightWrongLabel及AnswerText的塊
塊的類型 | 所在抽屜 | 作用 |
---|---|---|
set RightWrongLabel.Text to | RightWrongLabel | 需要清空內(nèi)容的label |
“” | Text | 當(dāng)用戶點(diǎn)擊“下一題”時(shí),刪除對(duì)上一題回答的反饋 |
set AnswerText.Text to | AnswerText | 用戶對(duì)上一題的回答 |
“” | Text | 當(dāng)用戶點(diǎn)擊“下一題”時(shí),刪除對(duì)上一題的回答 |
用戶單擊“下一題”時(shí),圖8-15中的前兩行用于清空RightWrongLabel和AnswerText。
圖 8-15 當(dāng)轉(zhuǎn)入下一題時(shí),清空上一題的答案及對(duì)答案的反饋
測試:回答一個(gè)問題,然后點(diǎn)擊“提交”,再單擊Next按鈕,上一題的答案及反饋是否消失了?
圖8-16與8-17顯示了“總統(tǒng)測驗(yàn)”應(yīng)用中塊的最終配置。
圖 8-16 “總統(tǒng)測驗(yàn)”應(yīng)用中塊的最終配置(之一)
圖 8-17 “總統(tǒng)測驗(yàn)”應(yīng)用中塊的最終配置(之二)
一旦測驗(yàn)應(yīng)用開始正常運(yùn)行,你也許會(huì)樂于做一些改進(jìn),例如:
現(xiàn)在應(yīng)用中只顯示與問題有關(guān)的圖片,也可以嘗試播放錄音或視頻片段。在使用聲音上,你甚至可以發(fā)展出一款“辯聲識(shí)曲(Name That Tune)”的應(yīng)用;
本測驗(yàn)對(duì)正確答案的要求過于嚴(yán)格,有幾種改進(jìn)方法:一是使用text.contains塊,來檢查是用戶的輸入中是否包含了真正的答案;另一種方法是給每道題提供多個(gè)答案,通過遍歷(foreach)來檢查是否與標(biāo)準(zhǔn)答案相匹配;你還可以想辦法處理掉那些用戶輸入的多余空格,或者不做大小寫區(qū)分,等等;
下面是本教程中所涉及到的概念:
將應(yīng)用程序劃分為數(shù)據(jù)(通常保存在列表中)及事件處理程序兩個(gè)部分;使用Ifelse塊來做條件判斷,有關(guān)條件語句的更多信息,請(qǐng)參見第18章;
在事件處理程序中,程序塊只能引用抽象的名稱來指代列表項(xiàng)及列表長度,以便當(dāng)列表數(shù)據(jù)發(fā)生變化時(shí),程序還可以正常運(yùn)行;
更多建議: