面向大眾的移動(dòng)技術(shù): Overheard Word 的單詞和手勢
以編程方式將第三方的代碼集成到您的 Android UI
從 GitHub 或另一個(gè)存儲(chǔ)庫采集第三方代碼,可能讓您覺得自己像在糖果店里的小孩一樣,但還是需要一些技巧來將這些代碼與您的 Android UI 相集成。這個(gè)月,Andrew Glover 將向您展示如何利用基于 JSON 的單詞引擎和一些預(yù)焙制的滑動(dòng)手勢功能,將 Overheard Word 演示應(yīng)用程序提升一個(gè)層次。事實(shí)證明,Android 可以輕松容納第三方代碼,但如果希望應(yīng)用程序的 UI 可以順利地運(yùn)行它,您仍然必須執(zhí)行一些縝密的邏輯。
如果您到目前為止已經(jīng)閱讀并遵循本系列中的演示,那么就已經(jīng)建立了一些基本的 Android 開發(fā)技能。除了搭建一個(gè) Android 開發(fā)環(huán)境并編寫您的***個(gè) Hello World 應(yīng)用程序,您已經(jīng)學(xué)會(huì)了如何在一個(gè)自定義的移動(dòng)應(yīng)用程序中用滑動(dòng)手勢代替按鈕輕觸,并實(shí)施菜單(或工具欄)和圖標(biāo)。在本文中,您繼續(xù)沿著這條軌跡,學(xué)習(xí)如 何使用第三方庫來增加或增強(qiáng)應(yīng)用程序的功能。首先,我們將安裝一些開源庫并讀取文件,然后將以編程方式集成新的功能與一個(gè)演示應(yīng)用程序的 UI。
正如我在以前的文章中所做的,我會(huì)用我的 Overheard Word 應(yīng)用程序進(jìn)行演示。如果您還沒有克隆 Overheard Word 的 GitHub 庫,您應(yīng)該先這成這一步,以便可以執(zhí)行后面的步驟。
Thingamajig:一個(gè)可插拔的單詞引擎
Overheard Word 是英語語言的應(yīng)用程序,可以幫助用戶學(xué)習(xí)新單詞,并動(dòng)態(tài)建立詞匯表。在以前的文章中,我們首先開發(fā)了一個(gè)基本的應(yīng)用程序,然后增加了 滑動(dòng)手勢 實(shí)現(xiàn)更方便的導(dǎo)航,并增加了 圖標(biāo) 形成更漂亮的 UI。到目前為止,一切都很好,但這個(gè)應(yīng)用程序不會(huì)走得很遠(yuǎn),因?yàn)樗鄙倌撤N成分:Overheard Word 需要一些單詞!
為了使 Overheard Word 成為真正的多詞 應(yīng)用程序,我已經(jīng)建立了一個(gè)小型單詞引擎 Thingamajig,它封裝單詞的概念及其相應(yīng)的定義。Thingamajig 處理通過 JSON 文檔創(chuàng)建單詞及其定義,并且完全沒有依賴于 Android。像 Overheard Word 應(yīng)用程序一樣,我的單詞引擎 托管在 GitHub 上。您可以克隆存儲(chǔ)庫,或下載源代碼,然后運(yùn)行 ant
。
您***會(huì)獲得一個(gè) 8KB 的輕量級(jí) JAR 文件,可以將該文件復(fù)制到您的 libs
目錄中。如果您的 Android 項(xiàng)目已正確設(shè)置,那么 IDE 會(huì)自動(dòng)將 libs
目錄中的任何文件識(shí)別為一個(gè)依賴關(guān)系。
當(dāng)時(shí),我的單詞引擎中的代碼是通過 JSON 文檔實(shí)例進(jìn)行初始化的。JSON 文檔可以是駐留在設(shè)備上的文件系統(tǒng)中的一個(gè)文件、對 HTTP 請求的響應(yīng),甚至是對數(shù)據(jù)庫查詢的響應(yīng) — 就目前來說,這并不重要。重要的是,您有一個(gè)實(shí)用的庫,讓您可以使用 Word
對象。每個(gè) Word
對象包含一個(gè) Definition
對象的集合,以及一個(gè)相應(yīng)的詞性。雖然單詞及其定義之間的關(guān)系遠(yuǎn)不是如此簡單,但它現(xiàn)在是可行的。之后,我們可以添加例句、同義詞和反義詞。
Android 中的第三方庫
將第三方庫集成到 Android 項(xiàng)目很容易;事實(shí)上,每一個(gè) Android 啟動(dòng)項(xiàng)目都包括一個(gè)特殊的目錄 libs
,您可以將第三方 JAR 放在該目錄中。在 Android 的構(gòu)建過程中,普通的 JVM 文件和第三方 JAR 都被轉(zhuǎn)換為可以兼容 Dalvik(這是專用的 Android VM)。
除了將單詞引擎庫插入到我的應(yīng)用程序中,我還準(zhǔn)備添加另一個(gè) 名稱為 Gesticulate 的第三方應(yīng)用程序。就像使用單詞引擎庫一樣,您可以通過從 GitHub 上 克隆或下載 它的源代碼來獲得 Gesticulate。然后運(yùn)行 ant
,您會(huì)得到一個(gè) JAR 文件,您可以將這個(gè)文件放進(jìn)應(yīng)用程序的 libs
目錄。
更新 UI
現(xiàn)在,我們進(jìn)入本文的核心,即更新 UI,以集成您剛剛免費(fèi)搶購到的所有第三方代碼。幸運(yùn)的是,我利用我的 Overheard Word 應(yīng)用程序提前為這一刻做好了計(jì)劃?;氐轿揖帉懺搼?yīng)用程序的 ***次迭代 時(shí),我定義了一個(gè)簡單的布局,其中包括一個(gè)單詞、其詞性,以及一個(gè)定義,如 圖 1 所示:
圖 1. Overheard Word 的默認(rèn)視圖
該布局使用占位符文本定義,我準(zhǔn)備使用從我的可插拔單詞引擎獲取的實(shí)際單詞替換占位符文本。所以,我會(huì)初始化一系列單詞,抓取其中一個(gè),并使用其值相應(yīng)地更新 UI。
為了更新 UI,我必須能夠獲得每個(gè)視圖元素的一個(gè)句柄,并為這些元素提供值。例如,一個(gè)顯示的單詞(如 Pedestrian)被定義為一個(gè) TextView
,其 ID 是 word_study_word
,如 清單 1 所示:
清單 1. 在一個(gè)布局中定義的 TextView
- <TextView
- android:id="@+id/word_study_word"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:layout_marginBottom="10dp"
- android:layout_marginTop="60dp"
- android:textColor="@color/black"
- android:textSize="30sp"
- android:text="Word"/>
如果您仔細(xì)看的話,就會(huì)發(fā)現(xiàn)我已經(jīng)在 XML 中將文本設(shè)置為 “Word
”;但是,我能夠以編程方式,通過獲取 TextView
實(shí)例的引用,并調(diào)用 setText
來設(shè)置該值。
在可以進(jìn)行下一步之前,我需要建立一個(gè)單詞列表。您或許還記得我的單詞引擎可以通過 JSON 文檔創(chuàng)建 Word
實(shí)例,因此,我需要做的只是設(shè)置我的 Android 應(yīng)用程序,讓它包括并讀取這些自定義文件。
使用文件:res 和 raw 目錄
默認(rèn)情況下,一個(gè) Android 應(yīng)用程序具有讀取設(shè)備的基礎(chǔ)文件系統(tǒng)的權(quán)限,但您只可以訪問應(yīng)用程序本身下面的本地文件系統(tǒng)。因此,您可以在您的應(yīng)用程序中包括文件,并相應(yīng)地引用它們。(這意味著,您可以讀取和寫入相對于您的應(yīng)用程序是本地 的文件;寫入到 SD 卡等在您的應(yīng)用程序之外的文件系統(tǒng)則需要專門的權(quán)限。)因?yàn)槲业膯卧~引擎可以采用一個(gè) JSON 文檔來初始化單詞列表,所以就沒有什么可以阻止我包括一個(gè)應(yīng)用程序會(huì)在運(yùn)行時(shí)讀取的 JSON 文檔。
就像在我以前的文章中所演示的圖標(biāo)資產(chǎn)一樣,您可以將文件存儲(chǔ)在 res
目錄中。如果我發(fā)現(xiàn)自己需要自定義文件,我喜歡將它們添加到被稱為 raw
的一個(gè)目錄中。我放在該目錄中的任何文件都可以通過生成的 R
文件來引用,我在 Overheard Word 中已經(jīng)使用過幾次該文件?;旧?,Android 平臺(tái)利用來自 res
目錄的資產(chǎn),并建立一個(gè)名稱為 R
的類,然后,該類為這些資產(chǎn)提供一個(gè)句柄。如果資產(chǎn)是一個(gè)文件,那么 R
文件將提供一個(gè)引用,以打開文件并獲取其內(nèi)容。
我在 res
目錄結(jié)構(gòu)中創(chuàng)建一個(gè) raw
目錄,并將一個(gè) JSON 文檔放在該目錄中,如 圖 2 所示:
圖 2. 包含新單詞的 raw 目錄
接下來,Eclipse 重新構(gòu)建項(xiàng)目,而我的 R
文件可以方便地引用新文件,如 圖 3 所示:
圖 3. 更新后的 R 文件
當(dāng)我有一個(gè)文件的句柄時(shí),我就可以打開它,讀取它,并最終構(gòu)建一個(gè) JSON 文檔來作為生成一個(gè)單詞列表的基礎(chǔ)。
構(gòu)建一個(gè)單詞列表
當(dāng)應(yīng)用程序啟動(dòng)時(shí),我就開始執(zhí)行一系列步驟,加載原始 JSON 文檔,并構(gòu)建一個(gè)單詞列表。我將創(chuàng)建一個(gè)名稱為 buildWordList
的方法來處理這些步驟,如 清單 2 所示:
清單 2. 在 Android 中讀取文件的內(nèi)容
- private List<Word> buildWordList() {
- InputStream resource =
- getApplicationContext().getResources().openRawResource(R.raw.words);
- List<Word> words = new ArrayList<Word>();
- try {
- StringBuilder sb = new StringBuilder();
- BufferedReader br = new BufferedReader(new InputStreamReader(resource));
- String read = br.readLine();
- while (read != null) {
- sb.append(read);
- read = br.readLine();
- }
- JSONObject document = new JSONObject(sb.toString());
- JSONArray allWords = document.getJSONArray("words");
- for (int i = 0; i < allWords.length(); i++) {
- words.add(Word.manufacture(allWords.getJSONObject(i)));
- }
- } catch (Exception e) {
- Log.e(APP, "Exception in buildWordList:" + e.getLocalizedMessage());
- }
- return words;
- }
您應(yīng)該注意到在 buildWordList
方法中執(zhí)行的一些操作。首先,請注意如何使用 Android 平臺(tái)的調(diào)用創(chuàng)建 InputStream
,Android 平臺(tái)的調(diào)用最終引用在 raw
目錄中發(fā)現(xiàn)的 words.json
文件。我沒有使用 String
來代表路徑,這使得代碼可跨多種設(shè)備進(jìn)行移植。接下來,我使用包含 在 Android 平臺(tái)中的一個(gè)簡單的 JSON 庫,將內(nèi)容(通過 String
表示)轉(zhuǎn)換成一系列的 JSON 文檔。在 Word
上的靜態(tài)方法 manufacture
讀取一個(gè) JSON 文檔,該文檔代表一個(gè)單詞。
單詞的 JSON 格式如 清單 3 所示:
清單 3. JSON 代表一個(gè)單詞
- {
- "spelling":"sagacious",
- "definitions":[
- {
- "part_of_speech":"adjective",
- "definition":"having or showing acute mental discernment
- and keen practical sense; shrewd"
- }
- ]
- }
我的 WordStudyEngine
類是 Thingamajig 的主要外觀。它從 Word
實(shí)例列表產(chǎn)生隨機(jī)單詞和函數(shù)。所以,我的下一步是利用新建的 Word
List
初始化引擎,如 清單 4 所示:
清單 4. 初始化 WordStudyEngine
- List<Word> words = buildWordList();
- WordStudyEngine engine = WordStudyEngine.getInstance(words);
當(dāng)有一個(gè)已初始化的引擎實(shí)例,我就可以向其請求一個(gè)單詞(自動(dòng)隨機(jī)排列),然后相應(yīng)地更新 UI 的三個(gè)元素。例如,我可以更新定義的單詞 部分,如 清單 5 所示:
清單 5. 以編程方式更新 UI 元素
- Word aWord = engine.getWord();
- TextView wordView = (TextView) findViewById(R.id.word_study_word);
- wordView.setText(aWord.getSpelling());
清單 5 中的 findViewById
是一個(gè) Android 平臺(tái)調(diào)用,它讀取一個(gè)整數(shù) ID,您可以從您的應(yīng)用程序的 R
類中獲得該 ID。您能夠以編程方式將文本設(shè)置為 TextView
。您也可以設(shè)置字體類型,字體顏色,或文字顯示的大小,類似于這樣:wordView.setTextColor(Color.RED)
。
在 清單 6 中,我基本上按照相同的流程來更新應(yīng)用程序的 UI 的定義和詞性元素:
清單 6. 更多編程式更新
- Definition firstDef = aWord.getDefinitions().get(0);
- TextView wordPartOfSpeechView = (TextView) findViewById(R.id.word_study_part_of_speech);
- wordPartOfSpeechView.setText(firstDef.getPartOfSpeech());
- TextView defView = (TextView) findViewById(R.id.word_study_definition);
- defView.setText(formatDefinition(aWord));
請注意在 清單 5 和 清單 6 中,如何使用 R
文件按名稱引用布局元素。駐留在我的 Activity
類中的 formatDefinition
方法讀取一個(gè)定義字符串,并將其首字母變成大寫。該方法還格式化字符串,若句末沒有句號(hào),它就會(huì)在句末使用一個(gè)句號(hào)。(請注意,Thingamajig 與格式化沒有任何關(guān)系 — 它只是一個(gè)單詞引擎?。?/p>
我已完成這些 UI 元素的更新,所以就可以啟動(dòng)我的應(yīng)用程序,并檢查結(jié)果。瞧!我現(xiàn)在要學(xué)習(xí)一個(gè)合法的單詞!
圖 4. Overheard Word 有單詞了!
添加手勢:將滑動(dòng)連接到單詞
現(xiàn)在,我可以有效地顯示一個(gè)單詞,我希望讓用戶能夠快速滑動(dòng)我的單詞引擎中的所有單詞。為了更簡單,我準(zhǔn)備使用 Gesticulate,這是我自己的第三方庫,可以計(jì)算滑動(dòng)速度和方向。我也把滑動(dòng)邏輯放進(jìn)一個(gè)名稱為 initializeGestures
的方法中。
初始化了滑動(dòng)手勢后,***步是將顯示一個(gè)單詞的邏輯移動(dòng)到一個(gè)新方法中,當(dāng)有人滑動(dòng)時(shí),我可以調(diào)用該方法來顯示一個(gè)新單詞。更新后的 onCreate
方法(最初是在 Android 創(chuàng)建應(yīng)用程序?qū)嵗龝r(shí)調(diào)用它)如 清單 7 所示:
清單 7. 當(dāng)初始化手勢時(shí),onCreate 即可顯示一個(gè)單詞
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.d(APP, "onCreated Invoked");
- setContentView(R.layout.activity_overheard_word);
- initializeGestures();
- List<Word> words = buildWordList();
- if (engine == null) {
- engine = WordStudyEngine.getInstance(words);
- }
- Word firstWord = engine.getWord();
- displayWord(firstWord);
- }
請注意 engine
變量,我將它定義為 OverheardWord Activity
本身的一個(gè) private static
成員變量。我將簡單地解釋為什么要這樣做。
接下來,我進(jìn)入 initGestureDetector
方法,如果您按照所克隆的代碼,就會(huì)發(fā)現(xiàn)在 initializeGestures
方法中引用了它。如果你還記得,initGestureDetector
方法有一個(gè)邏輯,當(dāng)用戶在設(shè)備屏幕上向上、向下、向左或向右滑動(dòng)時(shí),它就會(huì)執(zhí)行一個(gè)操作。在 Overheard Word 中,當(dāng)用戶從右到左滑動(dòng)時(shí)(這是左滑),我想顯示一個(gè)新單詞。我首先刪除 Toast
消息,這是該代碼的占位符,將其替換為一個(gè)對 displayWord
的調(diào)用,如 清單 8 所示:
清單 8. 當(dāng)向左滑動(dòng)時(shí),initGestureDetector 即可顯示一個(gè)單詞
- private GestureDetector initGestureDetector() {
- return new GestureDetector(new SimpleOnGestureListener() {
- public boolean onFling(MotionEvent e1, MotionEvent e2,
- float velocityX, float velocityY) {
- try {
- final SwipeDetector detector = new SwipeDetector(e1, e2, velocityX, velocityY);
- if (detector.isDownSwipe()) {
- return false;
- } else if (detector.isUpSwipe()) {
- return false;
- } else if (detector.isLeftSwipe()) {
- displayWord(engine.getWord());
- } else if (detector.isRightSwipe()) {
- Toast.makeText(getApplicationContext(),
- "Right Swipe", Toast.LENGTH_SHORT).show();
- }
- } catch (Exception e) {}
- return false;
- }
- });
- }
我的單詞引擎由 engine
變量表示,它需要在整個(gè) Activity
中都可以被訪問。這就是為什么我將其定義為一個(gè)成員變量,以確保每次有左滑時(shí)都會(huì)顯示一個(gè)新單詞。
來回滑動(dòng)
現(xiàn)在,當(dāng)打開應(yīng)用程序,并開始滑動(dòng),每當(dāng)向左滑動(dòng)時(shí),我都會(huì)看到顯示一個(gè)新單詞和定義。但是,當(dāng)向其他方向滑動(dòng)時(shí),我只會(huì)得到一條微小的消息,顯示我已經(jīng)滑動(dòng)了。如果我能夠通過向右滑動(dòng)回到前一個(gè)單詞,豈不是更好嗎?
回退似乎不應(yīng)該是困難的。也許我可以使用一個(gè)堆棧,只是彈出由前一次左滑放在那里的***元素?但是,當(dāng)用戶在回退后再次左滑時(shí),這種想法是站不住腳的。如果***的位置被彈出,則將顯示一個(gè)新 單詞,而不是用戶以前看到的單詞。仔細(xì)思考后,我傾向于嘗試一個(gè)鏈表的方法,當(dāng)用戶來回滑動(dòng)時(shí),可以擺脫之前瀏覽的單詞。
我將首先創(chuàng)建所有已顯示單詞的一個(gè) LinkedList
,如 清單 9 所示。然后,我會(huì)保持讓一個(gè)指針指向該列表中的元素的索引,我可以用它來檢索已經(jīng)看到的單詞。當(dāng)然,我會(huì)將這些定義為 static
成員變量。我也將我的指針初始化為 -1
,并且每當(dāng)將一個(gè)新單詞添加到 LinkedList
實(shí)例時(shí),我就將其遞增。就像任何語言中大部分類似于數(shù)組支持的集合一樣,LinkedList
是從 0 開始建立索引。
清單 9. 新的成員變量
- private static LinkedList<Word> wordsViewed;
- private static int viewPosition = -1;
在 清單 10 中,我將我的應(yīng)用程序的 onCreate
中的 LinkedList
初始化:
清單 10. 初始化 LinkedList
- if (wordsViewed == null) {
- wordsViewed = new LinkedList<Word>();
- }
現(xiàn)在,當(dāng)顯示***個(gè)單詞時(shí),我需要將其添加到 LinkedList
實(shí)例(wordsViewed
)并遞增指針變量 viewPosition
,如 清單 11 所示:
清單 11. 不要忘記遞增視圖位置
- Word firstWord = engine.getWord();
- wordsViewed.add(firstWord);
- viewPosition++;
- displayWord(firstWord);
滑動(dòng)的邏輯
現(xiàn)在,我進(jìn)入邏輯 的主要部分,這需要一些思考。當(dāng)用戶向左滑動(dòng)時(shí),我想顯示下一個(gè)單詞,當(dāng)他或她向右滑動(dòng)時(shí),我要顯示前一個(gè)單詞。如果用戶再次向右滑動(dòng),那么應(yīng)該顯示第二 個(gè)之前的單詞,以此類推。應(yīng)該繼續(xù)該操作,直到用戶回到***個(gè)顯示的單詞。然后,如果用戶開始再次向左滑動(dòng),單詞列表應(yīng)該以和之前相同的順序出現(xiàn)。***的 部分有點(diǎn)棘手,因?yàn)槲业?WordStudyEngine
的默認(rèn)實(shí)現(xiàn)是以隨機(jī)的 順序返回一個(gè)單詞。
在 清單 12 中,我覺得自己已經(jīng)解決了想要執(zhí)行的操作的邏輯。***個(gè)布爾值賦值被封裝在一個(gè)稱為 listSizeAndPositionEql
的方法中,它很簡單:wordsViewed.size() == (viewPosition + 1)
。
如果 listSizeAndPositionEql
被賦值為 true,那么應(yīng)用程序通過 displayWord
方法顯示一個(gè)新單詞。然而,如果 listSizeAndPositionEql
是 false,那么用戶必須向后滑動(dòng);因此,使用 viewPosition
指針從列表中檢索相應(yīng)的單詞。
清單 12. 用于來回滾動(dòng)的 LinkedList
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
- try {
- final SwipeDetector detector = new SwipeDetector(e1, e2, velX, velY);
- if (detector.isDownSwipe()) {
- return false;
- } else if (detector.isUpSwipe()) {
- return false;
- } else if (detector.isLeftSwipe()) {
- if (listSizeAndPositionEql()) {
- viewPosition++;
- Word wrd = engine.getWord();
- wordsViewed.add(wrd);
- displayWord(wrd);
- } else if (wordsViewed.size() > (viewPosition + 1)) {
- if (viewPosition == -1) {
- viewPosition++;
- }
- displayWord(wordsViewed.get(++viewPosition));
- } else {
- return false;
- }
- } else if (detector.isRightSwipe()) {
- if (wordsViewed.size() > 0 && (listSizeAndPositionEql() || (viewPosition >= 0))) {
- displayWord(wordsViewed.get(--viewPosition));
- } else {
- return false;
- }
- }
- } catch (Exception e) {}
- return false;
- }
注意,如果 viewPosition
是 -1
,那么用戶已一直向后滑動(dòng)到起點(diǎn) — 在這種情況下,列表中的指針被遞增回到 0。這是因?yàn)榛?Java 的 LinkedList
實(shí)現(xiàn)中沒有 -1
元素。(在某些其他語言中,負(fù)位置值從列表的后部開始,所以 -1
是尾元素。)
一旦掌握了左滑,處理右滑邏輯就會(huì)相當(dāng)容易。實(shí)際上,如果在 wordsViewed
實(shí)例中有一個(gè)單詞,那么您需要使用一個(gè)遞減的指針值來訪問先前查看過的單詞。
試試看:啟動(dòng)一個(gè) Overheard Word 實(shí)例,并來回滑動(dòng),以學(xué)習(xí)一些單詞。每次滑動(dòng)時(shí),UI 將被相應(yīng)地更新。
結(jié)束語
Overheard Word 一直運(yùn)行得很好,現(xiàn)在,我們已經(jīng)開始在基本的 Android 外殼之上添加一些更有趣的特性。當(dāng)然,還有更多的事情要做,但我們現(xiàn)在擁有一個(gè)正常運(yùn)作的應(yīng)用程序,它可以處理滑動(dòng),有圖標(biāo)和菜單,甚至可以從一個(gè)自定義 的第三方單詞文件中讀取。下個(gè)月,我會(huì)告訴您如何在 Overheard Word 中添加更多樣式,然后實(shí)現(xiàn)一個(gè)靈活的測驗(yàn)特性。