首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

J2ME 低級 UI 實作數字輸入框:繞過高級 UI 輸入法痛點

     編輯:Java 相關知識

痛點:高級 UI TextField 數字輸入體驗極差

  • S40/Series 60 模擬器與真機:數字鍵需多次按鍵切換輸入法 (ABC→123→符號)
  • 輸入法彈窗遮擋畫面、響應延遲、焦點切換閃爍
  • 無法自定義按鍵映射、長按行為、視覺風格
  • 部分裝置 `TextField.NUMERIC` 仍啟用 T9 預測,反而干擾純數字輸入

解決方案:Canvas 低級 UI 自繪 + 直接鍵盤處理

繼承 `Canvas` (或 `GameCanvas`),在 `paint()` 繪製輸入框,在 `keyPressed()` 直接處理數字鍵 `KEY_NUM0`~`KEY_NUM9`,**完全繞過系統輸入法**,體驗媲美原生應用。

完整實作代碼

import javax.microedition.lcdui.*;

public class NumberInputCanvas extends Canvas {
    private StringBuffer inputBuffer = new StringBuffer();  // 輸入緩衝
    private int maxLength = 4;                               // 最大長度
    private int lastKey = -1;                                // 最後按鍵 (長按檢測用)
    
    // 顏色常數
    private static final int BG_COLOR = 0xFFFFFF;
    private static final int TEXT_COLOR = 0x000000;
    private static final int BORDER_COLOR = 0x888888;
    private static final int CURSOR_COLOR = 0xFF0000;

    public NumberInputCanvas() {
        setFullScreenMode(false);
    }

    /** 設定最大輸入長度 */
    public void setMaxLength(int len) {
        this.maxLength = len;
    }

    /** 取得輸入內容 */
    public String getInput() {
        return inputBuffer.toString();
    }

    /** 清空輸入 */
    public void clear() {
        inputBuffer.setLength(0);
        repaint();
    }

    protected void paint(Graphics g) {
        int w = getWidth();
        int h = getHeight();
        
        // 背景
        g.setColor(BG_COLOR);
        g.fillRect(0, 0, w, h);
        
        // 邊框
        g.setColor(BORDER_COLOR);
        g.drawRect(0, 0, w - 1, h - 1);
        
        // 文字內容置中顯示
        g.setColor(TEXT_COLOR);
        String text = inputBuffer.toString();
        int textW = g.getFont().stringWidth(text);
        int textH = g.getFont().getHeight();
        g.drawString(text, (w - textW) / 2, (h - textH) / 2, Graphics.TOP | Graphics.LEFT);
        
        // 閃爍游標 (簡易:根據時間奇偶顯示)
        if ((System.currentTimeMillis() / 500) % 2 == 0 && text.length() < maxLength) {
            g.setColor(CURSOR_COLOR);
            int cursorX = (w - textW) / 2 + textW + 2;
            int cursorY = (h - textH) / 2;
            g.drawLine(cursorX, cursorY, cursorX, cursorY + textH);
        }
    }

    protected void keyPressed(int keyCode) {
        int gameAction = getGameAction(keyCode);
        
        // 數字鍵 0-9 直接追加
        if (keyCode >= Canvas.KEY_NUM0 && keyCode <= Canvas.KEY_NUM9) {
            if (inputBuffer.length() < maxLength) {
                char digit = (char) ('0' + (keyCode - Canvas.KEY_NUM0));
                inputBuffer.append(digit);
                repaint();
            }
            return;
        }

        // 退格鍵 (通常是 # 鍵或左軟鍵)
        if (keyCode == Canvas.KEY_POUND || gameAction == Canvas.LEFT) {
            if (inputBuffer.length() > 0) {
                inputBuffer.deleteCharAt(inputBuffer.length() - 1);
                repaint();
            }
            return;
        }

        // 確認/提交 (通常是 * 鍵、右軟鍵、選擇鍵、FIRE)
        if (keyCode == Canvas.KEY_STAR || gameAction == Canvas.FIRE || gameAction == Canvas.RIGHT) {
            if (inputBuffer.length() > 0) {
                int number = Integer.parseInt(inputBuffer.toString());
                onInputConfirmed(number);  // 回調處理
            }
            return;
        }

        // 長按 # 鍵清空 (需裝置支援 hasRepeatedEvents)
        if (keyCode == Canvas.KEY_POUND && hasRepeatedEvents()) {
            // 實際長按邏輯在 keyRepeated 處理
        }
    }

    protected void keyRepeated(int keyCode) {
        // 長按 # 鍵:清空全部
        if (keyCode == Canvas.KEY_POUND && inputBuffer.length() > 0) {
            inputBuffer.setLength(0);
            repaint();
        }
    }

    /** 輸入確認回調,子類覆寫或設定 Listener */
    protected void onInputConfirmed(int number) {
        System.out.println("輸入確認: " + number);
        // 實際應用:切換畫面、發送指令、驗證密碼等
    }
}

關鍵技術點解析

技術點說明
直接判斷 `KEY_NUM0`~`KEY_NUM9`繞過輸入法,單次按鍵即輸入一個數字,零延遲
`StringBuffer` 緩衝高效字串拼接,避免 `String` 不可變產生大量臨時物件
`getGameAction()` 跨平台映射將裝置特定鍵碼映射為標準遊戲動作 (LEFT, RIGHT, FIRE 等),提升相容性
閃爍游標`System.currentTimeMillis() / 500 % 2` 簡易實現 500ms 閃爍
`hasRepeatedEvents()` 檢測**關鍵**:並非所有裝置支援 `keyRepeated`,必須檢查後再依賴長按邏輯
回調模式 `onInputConfirmed`解耦輸入元件與業務邏輯,支援繼承覆寫或 Listener 模式

進階擴展功能

1. 輸入遮罩 (密碼模式)

private boolean passwordMode = false;

public void setPasswordMode(boolean enabled) {
    this.passwordMode = enabled;
}

protected void paint(Graphics g) {
    // ...
    String displayText = passwordMode ? 
        new String(new char[inputBuffer.length()]).replace('\0', '*') : 
        inputBuffer.toString();
    // 繪製 displayText...
}

2. 格式化輸入 (日期/時間/電話)

// 自動插入分隔符
if (inputBuffer.length() == 2 || inputBuffer.length() == 5) {  // HH:MM:SS
    inputBuffer.append(':');
}
// 顯示時用 formatText,實際值用 rawBuffer

3. 最大長度動態調整 + 滾動顯示

// 輸入超過可見寬度時,左側截斷顯示 "...4567"
int visibleChars = getWidth() / g.getFont().charWidth('0');
String displayText = inputBuffer.length() > visibleChars ?
    "..." + inputBuffer.substring(inputBuffer.length() - visibleChars + 3) :
    inputBuffer.toString();

4. 震動/音效回饋

// 需 MIDP 2.0 + MMAPI
try {
    Player player = Manager.createPlayer("device:vibrator");
    player.start();  // 短震動確認按鍵
} catch (Exception e) { /* 忽略 */ }

相容性測試清單

  • [ ] S40 第 1/2/3 版 (Nokia 6300, 6500, 5310...)
  • [ ] Series 60 v3/v5 (Nokia N73, N95, 5800...)
  • [ ] Sony Ericsson JP-8/JP-9 平台
  • [ ] Motorola P2K/Linux 平台
  • [ ] 鴻蒙/國產功能機平台 (華為、中興、TCL...)
  • [ ] `hasRepeatedEvents()` 行為驗證 (長按清空是否生效)
  • [ ] 直式/橫屏切換下佈局自適應

總結

J2ME 低級 UI 自繪數字輸入框,**核心優勢在於「直接響應硬體鍵盤」**:

  1. **零輸入法干擾**:數字鍵即按即得,體驗流暢
  2. **完全可控**:外觀、長按、遮罩、格式、回饋全可定製
  3. **極致輕量**:單類別 < 100 行,無額外依賴,記憶體占用極小
  4. **跨平台穩健**:`getGameAction()` + `hasRepeatedEvents()` 應對碎片化

這是 J2ME 時代「因限制而生的創新」典範——**當高級 API 不夠好時,低級 API 給你完全的自由**。同樣的思想在現代 Android `View` 自定義、iOS `UIView` 繪製、Web Canvas 渲染、嵌入式裸機 UI 開發中依然閃耀。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved