在 Java ME(Micro Edition)資源受限環境下,標準高級 UI 的 TextBox 雖功能完整但樣式固定、難以定製。本文展示如何基於 GameCanvas 低級繪圖 API,打造一個自繪、帶閃爍游標、支援彈出系統輸入法的 TextEdit 組件,並整合進用戶註冊頁面實戰演示。
Graphics.drawString() 繪製文本,drawLine() 繪製游標,通過 clipRect 限制繪製區域防止溢出。PopUpTextBox(繼承 TextBox) → 用戶輸入確認 → 回調將字符串回填至 TextEdit。TextEdit 所在 Canvas 實現 Runnable,獨立線程每 500ms 切換游標顯示狀態並重繪。TextEdit // 核心繪製邏輯:drawTextBox(), drawCursor()
↑
UserRegist // 業務頁面:管理 3 個 TextEdit(用戶名/密碼/確認密碼)
↑
PopUpTextBox // 系統輸入法代理:處理確認/取消回調
↑
UIController // MVC 控制器:事件分發與屏幕切換
public void drawTextBox(GameCanvas canvas, Graphics g, String text,
int x, int y, boolean cursorBlinkOn) {
int padding = 4, margin = 2;
Font ft = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
g.setFont(ft);
// 計算寬高
width = 3 * canvas.getWidth() / 5 + 2 * padding;
height = ft.getHeight() + 2 * padding;
// 繪製邊框、背景、文本
g.setColor(Color.frame);
g.fillRect(x+1, y+1, width+margin, height+margin); // 陰影
g.setColor(Color.frameBg);
g.drawRect(x, y, width, height); // 邊框
g.setColor(Color.background);
g.fillRect(x+1, y+1, width-1, height-1); // 背景
g.setColor(Color.text);
g.drawString(text, x+padding, y+padding, Graphics.TOP|Graphics.LEFT);
// 繪製游標
drawCursor(g, x + ft.stringWidth(text) + padding, y+padding,
1, ft.getHeight(), cursorBlinkOn);
canvas.flushGraphics(x, y, width, height); // 部分刷新優化
}
// UserRegist 實現 Runnable
public void run() {
while (true) {
checkTimeStamp(); // 每 500ms 切換當前編輯框的 cursorBlinkOn
redraw(); // 重繪三個編輯框
synchronized(this) { wait(50L); } // 降低 CPU 佔用
}
}
private void checkTimeStamp() {
long now = System.currentTimeMillis();
if (lastCaretBlink + 500 < now) {
// 僅翻轉當前編輯框的游標狀態
if ("regist_name".equals(editor)) cursorBlinkOn1 = !cursorBlinkOn1;
else if ("regist_passwd".equals(editor)) cursorBlinkOn2 = !cursorBlinkOn2;
else cursorBlinkOn3 = !cursorBlinkOn3;
lastCaretBlink = now;
}
}
// UserRegist 按鍵事件
protected void keyPressed(int keyCode) {
if (keyCode == KEY_EDIT || isNumberKey(keyCode)) {
Object[] args = {object_name, editor, username, passwd, passwd_re};
controller.handleEvent(EVENT_USER_REGIST_EDIT, args);
}
}
// UIController 分發
case EVENT_USER_REGIST_EDIT:
textBox.init(args); // 傳遞當前值保持顯示
midlet.setCurrent(textBox); // 切換到系統 TextBox
break;
// PopUpTextBox 確認回調
protected void closeTextBox(boolean update) {
if (update) canvasText = getString(); // 獲取輸入值
// 根據 editor 字段將值回填到對應參數位置
Object[] backArgs = {object_name, editor, newUsername, newPasswd, newPasswdRe};
controller.handleEvent(EVENT_USER_REGIST_EDIT_BACK, backArgs);
}
| 挑戰 | 解決方案 |
|---|---|
| 低級 UI 無輸入法 | 委託高級 TextBox,裝飾器模式無縫切換 |
| 多編輯框游標管理 | 獨立 boolean 旗標 + 單線程定時器輪詢 |
| 文本不換行截斷 | clipRect 限制繪製區 + stringWidth 計算游標位置 |
| 屏幕適配 | 寬高按 canvas.getWidth()/Height() 比例動態計算 |
| 數據同步 | 彈出前帶走舊值,確認後回調覆蓋,取消則丟棄 |