觸控設備的Qwerty鍵盤畫布

這一篇是我在A Canvas Qwerty Keyboard For Touch Devices的中文翻譯,網址在觸控設備的Qwerty鍵盤畫布
為了備份,並轉貼在此:

使用低階UI的鍵盤問題

虛擬鍵盤背後的意圖就是再沒有實體鍵盤的觸控設備上像是Nokia 5800 XpressMusic及Nokia 5530 XpressMusic手機在MIDlet(內建低階UI類別)裡有一個機制來輸入任何種類的文字。

當使用有實體鍵盤的設備來建立低階(Canvas或GameCanvas)應用程式時,能夠從所按的鍵來取得事件然後據此行動,每個鍵的ascii碼會傳到Canvas類別的keyPressed(int)方法裡。

文數鍵盤: 只有一個ascii碼會傳到keyPressed方法,這是因為每個鍵參考幾個字元的話就不能去確切知道使用者想要輸入什麼字元,通常ascii碼傳到這個方法裡的會是關於數值的。

Qwerty鍵盤:假如鍵盤是qwerty,就能夠獲取所有字元集的ascii碼,當像shift或數值模式被按下時設備就會負責改變字元集,所以正確的ascii碼可以傳到這個方法裡。

根據上述,在不同的諾基亞手機裡輸入文字可以用qwerty鍵盤來解決,而且在文數鍵盤及非實體鍵盤的手機裡可以存在。

有一種方法來解決的基本問題,它包含了結合低階及高階的顯示元件,當一個低階應用程式的指定區域建立,那麼這個想法就是援引一個TextBox元件、擷取使用者新增的文字然後再一次在低階元件裡顯示。

這個方法有下面的缺點。

  • 高階元件會佔用整個螢幕。
  • TextBox元件的外觀感覺是根據原生的外觀感覺而不是使用應用程式的外觀感覺而這可能會干擾使用者經驗,舉一個例應用程式的外觀感覺整個是藍的而原生元件是黑的。

在最新的S60 5th版本裡有一個附加元件不會佔用整個螢幕,它只需要在JAD檔裡增加一個屬性,你可以看看範例如何使用這個機制在下面的連結裡。 How to use pop-up TextBox in Java ME

總體設計考慮

可繪區域

Image:vkimage1.jpg
在S60 5th版本裡的Canvas的可繪區域可以用屬性Nokia-MIDlet-On-Screen-Keypad來改變,不同的可繪區域大小顯示在下表裡。
Nokia-MIDlet-On-Screen-Keypad

Value Portrait Landscape
*Default (gameactions & navigation keys) 360×360 320×360
navigationkeys 360×384 372×360
no 360×640 640×360

方向

有一個方法可以強迫鍵盤保持橫向,可以使用下面的屬性這樣作:

Nokia-MIDlet-App-Orientation

可能值:portrait 或 landscape

很重要的備註一下是在早期5800的軟體版本裡這個屬性沒有作用,這個問題已在早期的韌體版本中更正。 KIJ001169 – Fixed landscape mode cannot be set by using Java ME in Nokia 5800 XpressMusic
理想情況下在JAD或manifest檔裡使用這兩個屬性就可以有一個全螢幕的虛擬鍵盤(640×360):

  • Nokia-MIDlet-On-Screen-Keypad: no
  • Nokia-MIDlet-App-Orientation: landscape

鍵盤組成

Image:vkimage2.jpg

TOUCHKEYBOARD

這個類別用在觸控設備的虛擬鍵盤,鍵盤由TextFieldArea用來顯示輸入的文字,及一系列可用字元的按鈕來允許修改虛擬鍵的行為等組成。

所有按鈕繼承自宣告基本方法的Button類別,虛擬鍵盤包含三種不同的按鈕。

  1. MatrixButton:是顯示字元鍵盤的一部份,每個鍵有一個預覽然後當按鈕下時採取一個行動。
  2. PushButton:是單獨的按鈕,這些按鈕按下時不會顯示這個鍵的預覽,例如Ok、 backspace、shift,按鈕釋放時也會有行動會採取。
  3. ModeButton:是改變鍵盤一些行為的按鈕,像是字元集或是啟用指針,當按鈕按下時採取行動。

改變字元區域的外觀感覺

MatrixButton也稱為字元區域是由TiledLayer組成,TiledLayer包含三種不同的拼布,第一塊用在鍵盤的閒置狀態,第二 塊用在鍵盤被選擇的狀態而第三塊則用在鍵盤按下時在右邊預覽,在MatrixButton裡的字元集會直接繪在GameCanvas的Graphics 上,他們的Font及Color可以用方法setCharactersAreaFont(Font)setCharactersAreaFontColor(int)setCharactersAreaPreviewFontColor(int)來改變,這允許字元區域來調整不同的尺寸來跟應用程式有相同的外觀感覺。

改變TextFieldArea的外觀感覺

要根據應用程式所用的外觀感覺來改變TextFieldArea的外觀感覺,可以使用下面的方法setTextFieldFont(Font)setTextFieldBkgColor(int)setTextFieldBorderColor(int)setCursorColor(int)setTextFieldFontColor(int)

改變其餘按鈕的外觀感覺

虛擬鍵盤其餘的按鈕是PushButton及ModeButton,這些按鈕基本上是由Sprite物件組成,要改變另一個按鈕的外觀感覺需要修改 resources資料夾裡相關的圖檔,每個圖檔會參考一個私有常數,所以假如圖檔名稱改變也要確認這個私有常數有變更是很重要的。

限制字元的數目

要限制鍵盤裡輸入的字元數目可以使用函式 setMaxSize(int)

TEXTFIELDAREA

這個元件能夠保持並顯示文字,新增時指定一個尺寸並在元件整個生命週期裡都保留著,TextFieldArea類似S60原生文字輸入區域的行為這個行為可以在寫入一個文字訊息時見到。

改變外觀感覺

根據應用程式所使用的外觀感覺來改變是可能的。

要這樣做使用下面的方法setTextFieldFont(Font)、setTextFieldBkgColor(int)、setTextFieldBorderColor(int)、setCursorColor(int)、setTextFieldFontColor(int)

安排文字

這個部份負責擁有的文字安排,要辨別一個字可以藉著字元後的空白字原來做,假如這個字太長不能適合這行的其餘空間,它會被推到下一行,假如單獨的一 個字還是大於這個TextFieldArea的水平空間,這個字就會被分成幾段來適合這個部份的寬度,每次一個新的字元新增或刪除整個textArea必 須重組,這是因為文字可以在任何地方輸入的關係。

關於游標

游標標記新的字元可以新增的地方,當新增發生時,游標移到下個位置,游標的顏色可以使用方法setCursorColor(int)來修改或使用setCursorVisible(boolean)來隱藏,游標可以使用方法moveCursorLeft()moveCursorRight()移 到左邊或右邊,當游標到最後一行的最後一個位置時,輸入的下一個字元會讓這部份向下移一行所以游標始終可以見到並且在最後一行,同樣地假如游標移到這部份 的右上方並且其上還隱藏有幾行文字,那麼他會使得這部份向上移一行所以游標還是保持可見,在這幾個字裡游標控制TextFieldArea顯示的己行文 字。

繪製TextFieldArea

這個部份需要一個Graphics物件來繪製,要這樣做需要一個明確的呼叫方法paint(Graphics, int, int)來做,最後兩個參數表示這部份將繪製的Graphics的xy位置,這機制的最大好處是這部份的同一實體可以不同的Graphics及不同的位置來繪製,這個部份會將文字分成幾行,只有在螢幕的幾行文字會被繪製,其餘的會被忽略,游標所在的那一行始終保持可見。

新增新的字元及取得文字

TextFieldArea最大字元數可以使用方法setMaxSize(int)來確定,假如沒有指定這個值,就不會限制輸入的字元數,要在目前游標位置新增一個新的字元可以使用方法insertCharacterInCursorPosition(char)來做,在游標位置刪除一個字元可以使用方法deleteLastCharBeforeCursor()來做,要在任何時間設定文字可以使用方法 setText(String),這個方法將置換原有的文字然後將游標置於該行的結尾,要取得TextFieldArea現有的文字使用getText()方法。 要充分利用鍵盤在設備裡的文字安排功能,這個部份提供insertAscii(int)方 法,當Canvas或GameCanvas使用時,keyPressed方法提供按鈕按下的ascii碼,對於正常鍵盤手機來說這是按下數字的 ascii,對qwerty設備來說它會是按下鍵的值,假如shift或數值鍵被按下該設備會自動改變字元集,所以所收到的ascii是不同的並且對應於 所要的鍵,倒退鍵字元也會以ascii碼來收取,所以沒有必要用明確的方式來呼叫刪除的函式。

下載原始碼及執行檔

在這裡你可以找到完整的NetBeans專案包括原始碼及執行檔。 Image:TouchKeyboard.zip

下載JAVADOCS

這裡你可以找到API參考。 Image:Javadocs.zip

下載範例

下面你將發現使用這個元件的範例,你可以下載完整的範例。 Image:KeyBoardExample.zip

使用元件

下面的範例顯示如何使用TouchKeyboard及TextFieldArea類別,這個範例包含有兩個TextFieldAreas的一個主要 的canvas,負責檢測屬於這個應用程式的使用者點到那個區域,一旦應用程式偵測到TextFieldArea有被接觸它就會顯示虛擬鍵盤,當使用者在 虛擬鍵盤選擇OK按鈕時,輸入的文字會儲在一個變數裡,這個變數是根據資料(name或address)的然後這個值會傳到正確的 TextFieldArea,下一次鍵盤援引時,它會提供TextFieldArea所存的文字。

記住TextFieldArea會調整它所持有的文字成自己定義的大小,那就是為什麼在TouchKeyboard裡的文字跟這兩個TextFieldAreas裡的文字可能會不同。

下面這張圖片顯示兩張應用程式的螢幕快照

Image:vkimage3.jpg

Image:vkimage4.jpg

JAD & Manifest屬性

Nokia-MIDlet-On-Screen-Keypad: no
Nokia-MIDlet-App-Orientation: landscape

BigKeyboard.java

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import keyboard.TextFieldArea;
import keyboard.TouchKeyboard;
import keyboard.VirtualKeyboardListener;

public class BigKeyboard extends MIDlet
        implements VirtualKeyboardListener {

    public static BigKeyboard instance;     //static instance for the MIDlet

    private TouchKeyboard tkb;              //Virtual keyboard

    private Display display;
    private CanvasScreen canvas;            //Main application canvas

    int nameTransId;                        //transaction id to identify name

    int addressTransId;                     //transaction id to identify address

    public BigKeyboard() {
        instance = this;
        canvas = new CanvasScreen();
        display = Display.getDisplay(this);
    }

    public void startApp() {
        showCanvas();
    }

    /**
     * Shows the main application Canvas
     */
    public void showCanvas() {
        display.setCurrent(canvas);
    }

    /**
     * Shows the virtual keyboard in order to retrieve the name.
     * The name will be limited to 20 characters.
     * @param name
     */
    public void getName(String name) {
        if (tkb == null) {
            createKeyboard();            //create keyboard if it does not exists
        }
        //when keyboard is reseted, it returns a transactionId
        nameTransId = tkb.resetKeyBoard();
        tkb.setMaxSize(20);
        tkb.setText(name);
        display.setCurrent(tkb);
    }

    /**
     * Shows the virtual keyboard in order to retrieve the name.
     * The name will be limited to 80 characters.
     * @param address
     */
    public void getAddress(String address) {
        if (tkb == null) {              //create keyboard if it does not exists
            createKeyboard();
        }
        //when keyboard is reseted, it returns a transactionId
        addressTransId = tkb.resetKeyBoard();
        tkb.setMaxSize(80);
        tkb.setText(address);
        display.setCurrent(tkb);
    }

    /**
     * Creates a new instance of the keyboard and sets it with the right
     * customization parameters
     */
    private void createKeyboard() {
        tkb = new TouchKeyboard(TouchKeyboard.KEYBOARD_BIG, 0, 0, false);
        tkb.setTextFieldBkgColor(0xE0DBCA);
        tkb.setTextFieldBorderColor(0x040477);
        tkb.setTextFieldFontColor(0x0033CC);
        tkb.setBackgroundColor(0x44A51C);
        tkb.togglePointer();
        tkb.setVirtualKeyboardListener(this);
    }

    public void pauseApp() {
    }

    public void destroyApp(boolean unconditional) {
    }

    /**
     * Method of the interface VirtualKeyboardListener.
     * This method is invoked when the OK button is pressed at the moment
     * the keyboard is displayed on the screen.
     * @param transactionId
     * @param text
     */
    public void okPressed(int transactionId, String text) {
        //use the transactionId to identify the purpose of the call
        if (transactionId == nameTransId) {
            canvas.setName(text);
        } else if (transactionId == addressTransId) {
            canvas.setAddress(text);
        }
        showCanvas();
    }
}

CanvasScreen.java

class CanvasScreen extends Canvas {

    /**
     * TextFieldArea containing the data of the name.  This textfield is only
     * to show information and so cursor should not be visible.
     */
    private TextFieldArea tfName;
    /**
     * TextFieldArea containing the data of the address. This textfield is only
     * to show information and so cursor should not be visible.
     */
    private TextFieldArea tfAddress;

    public CanvasScreen() {
        tfName = new TextFieldArea(200, 40);
        tfName.setCursorVisible(false);
        tfName.setTextFieldBorderColor(0xE024C1C);
        tfName.setTextFieldBkgColor(0xffffff);
        tfName.setTextFieldFontColor(0x0033CC);

        tfAddress = new TextFieldArea(200, 80);
        tfAddress.setCursorVisible(false);
        tfAddress.setTextFieldBorderColor(0xE024C1C);
        tfAddress.setTextFieldBkgColor(0xffffff);
        tfAddress.setTextFieldFontColor(0x0033CC);
    }

    /**
     * paints two rectangles in the screen
     * @param g
     */
    protected void paint(Graphics g) {
        g.setColor(68, 165, 28);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setColor(255, 255, 255);
        g.drawString("Name (max 20 chars):", 10, 10, 0);
        tfName.paint(g, 10, 40);
        g.setColor(255, 255, 255);
        g.drawString("Address (max 80 chars):", 10, 100, 0);
        tfAddress.paint(g, 10, 140);
    }

    /**
     * sets the text of the TextFieldArea destined to name
     * @param name
     */
    public void setName(String name) {
        tfName.setText(name);
    }

    /**
     * sets the text of the TextFieldArea destined to address
     * @param address
     */
    public void setAddress(String address) {
        tfAddress.setText(address);
    }

    /**
     * Detects if user points the first or the second textFieldArea in the
     * touch device
     * @param x
     * @param y
     */
    protected void pointerPressed(int x, int y) {
        if (x > 10 && x < 210) {
            if ((y > 40) && (y < 80)) {       //Pointer hits the first textfield

                BigKeyboard.instance.getName(tfName.getText());
            } else if ((y > 140) && (y < 220)) {  //pointer hits the second textfield

                BigKeyboard.instance.getAddress(tfAddress.getText());
            }
        }
    }
}

感謝你看到這裡,很快就可以離開了,但最好的獎勵行動就是按一下幫我分享或留言,感恩喔~

點我分享到Facebook

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *