在 Java ME 的 Canvas/GameCanvas 低級 UI 中,長文本顯示面臨截斷、無滾動條、適配多種屏幕尺寸三大挑戰。本文展示一套基於座標變換的自適應分頁滾動方案,含完整源碼與核心算法解析。
String[] lines。pageCount = totalLines * lineHeight / visibleHeight + 1。visibleHeight = (visibleHeight / lineHeight) * lineHeight,避免半行截斷。g.translate(0, -pageIndex * visibleHeight),將目標頁內容移入可見區。pageIndex/pageCount 比例在右側繪製滑塊。public class ShowHelp extends GameCanvas implements Runnable {
private int pageCount; // 總頁數
private int currentPage = 0; // 當前頁
private int visibleHeight; // 可見區高度(行高整數倍)
private int dir = 0; // 滾動方向
public void show() {
Graphics g = getGraphics();
g.clipRect(0, 0, getWidth(), getHeight());
// 1. 繪製背景、標題欄、菜單欄...
// 2. 文本換行
Font ft = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_MEDIUM);
String longText = "1 滾動分頁顯示;\n2 滾動分頁顯示;\n...共 34 行";
String[] lines = StringDealMethod.format(longText, getWidth()-15, ft);
// 3. 計算分頁參數
int headerH = headHeight + menuHeight;
int bodyH = getHeight() - headerH;
visibleHeight = (bodyH / ft.getHeight()) * ft.getHeight(); // 對齊行高
int margin = (bodyH - visibleHeight) / 2;
pageCount = lines.length * ft.getHeight() / visibleHeight + 1;
// 4. 設置繪製裁剪區(僅正文區)
g.clipRect(0, headerH + margin, getWidth(), visibleHeight);
// 5. 座標變換:將當前頁內容平移進可見區
g.translate(0, -currentPage * visibleHeight);
// 6. 繪製所有行(實際繪製座標為絕對位置,translate 自動偏移)
g.setFont(ft);
g.setColor(Color.TEXT);
for (int i = 0; i < lines.length; i++) {
g.drawString(lines[i], 5, i * ft.getHeight() + headerH + margin,
Graphics.TOP | Graphics.LEFT);
}
// 7. 恢復座標系
g.translate(0, currentPage * visibleHeight);
// 8. 繪製滾動條
drawScrollBar(g);
flushGraphics();
}
private void drawScrollBar(Graphics g) {
int barH = getHeight() - headHeight - menuHeight;
int x = getWidth() - 4;
g.setColor(Color.GRAY);
g.fillRect(x, headHeight, 2, barH); // 軌道
g.setColor(Color.BLUE);
int thumbH = barH / pageCount;
int thumbY = headHeight + currentPage * barH / pageCount;
g.fillRect(x-1, thumbY, 4, thumbH); // 滑塊
}
// 鍵盤控制
protected void keyPressed(int keyCode) {
if (keyCode == Canvas.KEY_NUM4) { /* 返回主菜單 */ return; }
int action = getGameAction(keyCode);
if (action == Canvas.UP) {
if (currentPage > 0) { currentPage--; dir = -1; show(); }
} else if (action == Canvas.DOWN) {
if (currentPage < pageCount - 1) { currentPage++; dir = 1; show(); }
}
}
// Runnable 接口可用於自動滾動或游標閃爍,此處省略
}
visibleHeight = (bodyH / lineHeight) * lineHeight;
確保可見區高度是行高的整數倍,clipRect 裁剪時永遠只包含完整行,杜絕頂部/底部出現半截字符。
| 方案 | 優點 | 缺點 |
|---|---|---|
| translate 平移 | 代碼簡潔、繳用絕對座標、自動處理所有繪製調用 | 需記住恢復座標系、調試時邏輯座標與物理座標分離 |
| 手動計算 y = baseY - page*pageH | 直觀、無狀態副作用 | 每行繪製需額外運算、易出錯、擴展困難 |
推薦 translate:配合 clipRect 實現「虛擬畫布」模型,邏輯清晰。
所有尺寸均基於 getWidth()/getHeight() 動態計算,無硬編碼常數,可在 128x128 至 480x800 任意分辨率設備上正常運行。
keyReleased 後持續平移並衰減。pointerDragged,實時更新 translate 位移量。drawString 替換為逐段繪製。這套方案在 Java ME 經典項目(如電子書閱讀器、幫助文檔查看器)中大規模驗證,無第三方依賴、代碼量小、性能穩定,是資源受限設備上實現長文本滾動的參考實現。