2013年4月9日 星期二

[Android] Android Developer Note Text and Input 2

Creating an Input Method (見Android developer Creating an IME)

序言:

一個IME(input method editor, 輸入法編輯器)是一個使用者控制介面,
可以讓使用者輸入文字。
Android提供了一個可延伸的輸入法框架,
允許apps來提供使用者不同類型的輸入法,
像是螢幕上的鍵盤,或者甚至是用聲音來輸入。
一旦安裝以後,使用者就可以在系統設定選擇他們想要使用的IME,
並且在整個系統的操作來使用它;
一次只有一個IME會被啟動。

要將IME加到Android系統,你得做一個Android app,
裏頭包含了一個繼承了InputMethodService的class。
除此以外,你通常會做一個"settings"的activity,
用來傳遞選項到IME service。
你也可以定義出一個設定的UI,
用以顯示部分的系統設定。

這段文章包含了下面內容:
1. IME的生命週期
2. 在app的manifest裡定義IME組件。
3. IME API。
4. 設計一個IME UI。
5. 從IME發送文字到app。
6. 使用IME subtypes運作。

如果你從沒做過IMEs,你應該先讀這篇導引文:
Onscreen Input Methods
還有,包在SDK裡面的軟鍵盤的sample app,
可以讓你拿來修改用以建立起屬於你自己的IME。

The IME Lifecycle:

下面的圖表描述了一個IME的生命週期:



下面的區塊描述了如何去實作符合這個生命週期的UI,
以及和IME相關聯的code。

Declaring IME Components in the Manifest:

在Android系統裡,
一個IME是一個Android app,
當中含有一個特別的IME服務。
app的manifest檔裡必須要聲明這個服務,
去request必須的許可權限,
提供一個符合action action.view.InputMethod的intent filter,
並且提供了定義了IME的特性的metadata。
除此以外,要提供允許使用者修改IME的設定介面的話,
你可以定義一個"settings"的activity,讓它可以從系統設定被啟動。

下面的片段聲明了IME服務的部分。
它去請求了BIND_INPUT_METHOD的權限許可,
以讓服務可以將IME連結到系統,
設定一個符合action android.view.InputMethod的intent filter,
並定義IME的metadata:

<!-- Declares the input method service -->
    <service android:name="FastInputIME"
        android:label="@string/fast_input_label"
        android:permission="android.permission.BIND_INPUT_METHOD">
        <intent-filter>
            <action android:name="android.view.InputMethod" />
        </intent-filter>
        <meta-data android:name="android.view.im" android:resource="@xml/method" />
    </service>

下一段聲明了IME的settings activity。
它有一個篩ACTION_MAIN的intent filter,
指示了這個activity是主要的IME app的進入點:

    <!-- Optional: an activity for controlling the IME settings -->
    <activity android:name="FastInputIMESettings" 
        android:label="@string/fast_input_settings">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
        </intent-filter>
    </activity>

你也可以從IME的UI,提供直接對IME設定存取。

The Input Method API:

IMEs所用的classes,
可以在android.inputmethodservice
android.view.inputmethod的packages裡找到。
KeyEvent的class對於處理鍵盤字元來說很重要。

一個IME的核心部分就是service component,
它是繼承InputMethodService的class。
在實作普通的服務的生命週期之外,
這個class有callbacks可以提供給你的IME的UI,
處理使用者輸入,並且將文字發到現在有focus的field上。
預設來說,InputMethodService的class提供了大多數的實作,
用以管理IME狀態、可見性,
以及和現在的輸入區域做溝通。

下面的classes也很重要:

BaseInputConnection
定義了從一個InputMethod回到接收輸入的app之間溝通的頻道。
你可以使用它來讀游標附近的文字,
將文字提交到文字方塊,
以及傳送raw key events到app。
app必須繼承這個class,
而非去實作底層的interface InputConnection

KeyboardView
一個View的延伸,render了鍵盤,
並且對於使用者輸入的events做出回應。
鍵盤的layout被一個的Keyboard實例所指定,
你可以將其定義在一個XML檔裡面。

Designing the Input Method UI:

一個IME有兩個主要的視覺元素:
input view以及candidates view。
你只需要將相關於你所設計的input method的元素實作好就行了。

Input view:
input view是使用者以按鍵形式、手寫,或手勢輸入的地方的UI。
當IME第一次被顯示時,系統會呼叫onCreateInputView()的callback。
在你的method的實作裡面,
做出你想要IME window所顯示的layout,
並且回傳到系統。
這個片段是一個實作onCreateInputView() method的範例:

    @Override 
    public View onCreateInputView() { 
        MyKeyboardView inputView = 
            (MyKeyboardView) getLayoutInflater().inflate( R.layout.input, null);
    
        inputView.setOnKeyboardActionListener(this); inputView.setKeyboard(mLatinKeyboard); 
        
        return mInputView; 
    } 

在這個範例裡,
MyKeyboardView是一個自訂的KeyboardView的實作的實例,
它render了一個Keyboard
如果你在建立的是一個傳統的QWERTY鍵盤,
可以參見軟鍵盤的sample app,作為一個如何繼承KeyboardView class的範例。

Candidates view:(候選視窗)

candidates view是IME顯示可能的字詞校正或建議給使用者選取的UI位置。
在IME的生命週期裡,當系統準備顯示candidate view時,
它會呼叫onCreateCandidatesView()
在你的method實作裡,回傳一個顯示字詞建議的layout,
或者回傳null,如果你不想顯示任何東西。
(null的回應是預設值,
所以如果你沒有提供建議的話,
你就不用去實作這個)

UI design considerations:

這個段落描述了一些特定的UI designs考慮給IMEs。

Handling multiple screen sizes
你的IME的UI必須要能夠在不同螢幕大小上縮放,
且它也要能處理兩個轉向。
在非全頻輸入法模式中,
留下足夠的空間給text field及相關的內容,
使得IME不會用有超過一半的螢幕空間。
全螢幕的IME就不會有這個議題了。

Handling different input types
Android text fields允許你設定一個特定的input type,
比如說自由格式的文字、數字、URLs、email地址,
以及搜尋字串。當你實作一個新的IME時,
你需要去偵測每個field裡的input type,
並且提供適合的Interface給它。
但是,你不用將你的IME調整好,
來檢查使用者是否輸入了合法的input type;
那是擁有text field的app的責任。

舉例來說,
這邊是Android平台提供給文字及電話號碼輸入的
Latin IME的螢幕截圖:
 

當一個input field接收到focus,且你的IME開始時,
系統會呼叫onStartInputView()
傳進一個EditorInfo物件,
當中包含了text field的input type以及其他屬性的詳細內容。
在這個物件裡,inputType field包含了text field的input type。

inputType field是一個int,
它包含了不同input type設定的bit pattern。
要測試text field的input type,
使用常數TYPE_MASK_CLASS來做遮罩(mask),像這樣:

inputType & InputType.TYPE_MASK_CLASS 

input type的bit pattern可以是下面幾個值之一,包括了:

TYPE_CLASS_NUMBER
輸入數字的text field。
如前面顯示的螢幕截圖那樣,
Latin IME顯示了一個數字pad給這個type的fields。
TYPE_CLASS_DATETIME
一個text field,用以輸入日期和時間。
TYPE_CLASS_PHONE
一個text field,用以輸入電話號碼。
TYPE_CLASS_TEXT
一個text field,用以輸入所有受支援的字元。

這些常數在給InputType的參考文件裡有更多的描述。
inputType field可以包含其他bits,指示一個text field的變數型態,比方說像:

TYPE_TEXT_VARIATION_PASSWORD
一個TYPE_CLASS_TEXT的變數,作為輸入密碼用。
輸入的method會顯示dingbat而非實際的文字。
TYPE_TEXT_VARIATION_URI
一個TYPE_CLASS_TEXT的變數,作為輸入網址URL,
及其他Uniform Resource Identifiers(URIs)。
TYPE_TEXT_FLAG_AUTO_COMPLETE
一個TYPE_CLASS_TEXT的變數,
作為輸入app從字典,搜尋,或其他功能所"自動完成"的文字。

記住,
當測試這些變數時,
要使用適當的常數來對inputType作遮罩。
可用的遮罩常數在inputType的參考文件裡有列出來。

注意:
在你自己的IME裡,當你將它送到一個密碼field時,
確保你正確地處理文字。
不論是在輸入的view或者是在候選的view裡,
都要將在你的UI的密碼隱藏起來。
同時也要記得,你不應該將密碼存在裝置上。
想要了解更多的話,可以看Designing for Security的導覽。

Sending Text to the Application:

當使用者使用你的IME來輸入文字時,
你可以將文字透過傳送個別的key events,
或者藉由編輯在app的text field的游標附近的文字,
來傳送文字。
在任何一個狀況下,
你都是使用一個InputConnection的實例來傳送文字。
要取得這個實例的話,
呼叫InputMethodService.getCurrentInputConnection()

Editing the text around the cursor:

當你在處理編輯已在text field存在的文字時,
一些在BaseInputConnection裡面的更有用的methods如下:

getTextBeforeCursor()
回傳一個CharSequence
當中包含了所request的字母的數量。
(當前游標的位置的所有字母)
getTextAfterCursor()
回傳一個CharSequence
當中包含了所request的字母的數量。
(當前游標的位置的所有字母)
deleteSurroundingText()
刪除在現有的游標位置前後,
指定數字的字母數量。
commitText()
提交一個CharSequence到text field,並且設定一個新的游標位置。

舉例來說,
下面的片段展示了如何取代文字"Fell"到左邊,
並用"Hello!":

    InputConnection ic = getCurrentInputConnection();
    
    ic.deleteSurroundingText(4, 0);
    
    ic.commitText("Hello", 1);
    
    ic.commitText("!", 1);

Composing text before committing
如果你的IME做了文字預測,
或者需要多重的步驟來作出一個字型(glyph)或者字(word),
你可以將過程秀在text field,
直到使用者提交了這個字,
接著你可以將部分的造詞替代成完整的文字。

你也許可能對文字作特別處理,
透過加上一個"span"上去,
當你將其傳到InputConnection#setComposingText()。

下面的片段展示了如何在text field裡秀出過程。

    InputConnection ic = getCurrentInputConnection();

    ic.setComposingText("Composi", 1);
...

    ic.setComposingText("Composin", 1);
...

    ic.commitText("Composing ", 1);

下面的螢幕截圖顯示了這是如何顯示給使用者的:
  

Intercepting hardware key events:

儘管輸入的method視窗沒有外顯的焦點,
它先取得了硬體的key events,
且可以選擇去消耗他們或送到app。
舉例來說,
在造詞時,
你也許會想要消耗方向鍵用以在UI作瀏覽給候選的選擇。
你也許也想要追蹤back key,
來解掉從輸入method的視窗跳出來的東西。

要解譯硬體keys,複寫onKeyDown()onKeyUp()這兩個函式。
從Soft Keyboard的sample app可以看範例。

當你有不想自己去處理的keys時,
記得要去call自己的super()

Creating an IME Subtype:

子型態允許IME表達自己可以支援多種輸入模式和語言。
一個子型態可以表示:
1. 一個地區語言選項(locale)比如說像en_US或fr_FR
2. 一個輸入模式,像是聲音、鍵盤,及手寫。
3. 其他的輸入模式、格式,或者IME的性質,
比如像是10鍵或qwerty鍵盤的layouts。
基本上,這個mode可以是任何文字,
像是"keyboard", "voice"等等。

一個子型態也可以表露這些的組合。

子型態資訊被用在IME切換對話框,
這個對話框可以被用在notification bar和IME設定。
資訊也允許了framework去將特定的子型態直接帶起。
當你建立起一個IME,就用子型態的功能,
因為它幫助使用者來辨識及在不同IME語言和模式間切換。

使用<subtype>元素,
將子型態定義在輸入method的XML resource檔案的其中之一。
下面的片段定義了一個有兩個子型態的IME:
一個鍵盤子型態(locale是US English),
另一個是鍵盤子型態法語的language locale:

<input-method xmlns:android="http://schemas.android.com/apk/res/android"
        android:settingsActivity="com.example.softkeyboard.Settings"
        android:icon="@drawable/ime_icon"
    <subtype android:name="@string/display_name_english_keyboard_ime"
            android:icon="@drawable/subtype_icon_english_keyboard_ime"
            android:imeSubtypeLanguage="en_US"
            android:imeSubtypeMode="keyboard"
            android:imeSubtypeExtraValue="somePrivateOption=true"
    />
    <subtype android:name="@string/display_name_french_keyboard_ime"
            android:icon="@drawable/subtype_icon_french_keyboard_ime"
            android:imeSubtypeLanguage="fr_FR"
            android:imeSubtypeMode="keyboard"
            android:imeSubtypeExtraValue="foobar=30,someInternalOption=false"
    />
    <subtype android:name="@string/display_name_german_keyboard_ime"
            ...
    />
/>

要確保你的子型態有正確地在UI裡被標籤上的話,
使用%s來取得一個子型態標籤,
其和子型態的locale標籤是一樣的。
這在下面兩個片段被展示。
第一個片段秀出了一部分的輸入method的XML file:

<subtype
        android:label="@string/label_subtype_generic"
        android:imeSubtypeLocale="en_US"
        android:icon="@drawable/icon_en_us"
        android:imeSubtypeMode="keyboard" />

下一個片段是IME的strings.xml檔的一部分。
這個被輸入method UI定義來設定子型態的標籤,
所使用的字串resource是被定義如:

<string name="label_subtype_generic">%s</string>

這會將子型態的顯示名稱設成"English (United States)"在任何英文語言區域,
或者在其他的區域會設到適當的地方。

Choosing IME subtypes from the notification bar:

Android系統安排了所有的子型態,給所有的IMEs表露。
IME子型態被以其所屬的IME的模式來看待。
在通知列(notification bar)裡,
一個使用者可以選擇現在所設定的IME的可用的子型態,
如同下面所秀出來的截圖:



Choosing IME subtypes from System Settings:

使用者可以控制子型態如何被使用,
藉由在系統設定裡的"語言及輸入裝置"設定窗來做調整。
在軟鍵盤的sample裡,
InputMethodSettingsFragment.java的檔案
包含了一個實作IME設定裡子型態的啟動器。

請參考在Android SDK的sample裡會有更多資訊。

General IME Considerations:

這裡有幾個當你在實作IME的時候,所要考慮的其他東西:
1. 提供一個方法給使用者直接從IME的UI來設定選項。
2. 因為裝置上可能灌了多個IME,
應提供使用者從輸入method UI直接來切換到另一個IME的方法。
3. 快速喚起IME的UI。預讀或在要求時才讀大資源,
可以讓使用者當按到一個text field時可以盡可能快地看到IME。
Cache資源以及views作為連串的輸入法調用(invocations)。
4. 反過來說,在輸入法視窗隱藏後,
你應該要快點將占了大量記憶體的空間盡速釋放。
這樣app可以有足夠的記憶體來運作。
考慮使用一個延遲的訊息來釋放資源,
如果IME是處於隱藏的狀態數秒時。
5. 確保使用者可以輸入盡可能多的字母,
使用相關聯的語言或地區。
記得使用者可能使用標點符號在密碼或使用者名稱,
所以你的IME必須要提供不同的字母來讓使用者輸入密碼,
並得到與裝置存取的能力。

沒有留言:

張貼留言