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

J2ME 低階 UI 文字自動換行完整解決方案:從定寬到多語言支援

     編輯:J2ME

低階 UI (Canvas) 繪製大段文字(遊戲說明、RPG 對話)時,必須自行處理換行排版。本文從簡單定寬切割,演進到支援多語言、非等寬字體、單詞不拆分的完整解決方案。

階段一:固定字元數切割(中文適用)

final int CHARS_PER_LINE = 7;  // 實測一行容納字數
final String[] helpText = {
    "年份不詳的一個", "時代中,妖與人", "類都存在於世界",
    "上,並基礎為對", "立狀態,但是不", "消除有相處一起",
    "的可能,由於人", "類基礎已經接收", "世界上有妖的事",
    "實了。"
};

// 繪製
for (int i = 0; i < helpText.length; i++) {
    g.drawString(helpText[i], 5, 5 + 20*i, Graphics.TOP | Graphics.LEFT);
}

缺點:修改文字需重新手工切割;不同手機螢幕寬度/字體大小導致顯示異常(如 K700 一行只容 6 字)。

階段二:自動按字元數切割(解決修改困擾)

final int CHAR_LIMIT = 6;
private Vector autoWrap(String text) {
    Vector lines = new Vector();
    while (text.length() > 0) {
        if (text.length() > CHAR_LIMIT) {
            lines.addElement(text.substring(0, CHAR_LIMIT));
            text = text.substring(CHAR_LIMIT);
        } else {
            lines.addElement(text);
            text = "";
        }
    }
    return lines;
}

// 使用:將長字串拆行
String fullText = "在年份不詳的一" + "個時代中,妖" + "與人類都存在" + ...;
Vector lines = autoWrap(fullText);
for (int i = 0; i < lines.size(); i++) {
    g.drawString((String)lines.elementAt(i), 5, 5+20*i, Graphics.TOP|Graphics.LEFT);
}

優點:文字修改只需改長字串,自動重新排版。缺點:不支援英文等非等寬字體,單詞會被強制拆分。

階段三:基於像素寬度的智慧換行(支援多語言)

核心 API:Font.stringWidth(String) 回傳字串實際繪製寬度(像素)。

演算法邏輯

  1. 以 stringWidth 為依據,二分搜尋或線性嘗試找出「最長不超過行寬」的切割點
  2. 遇到換行符 \n 強制換行
  3. 若切割點落在單詞中間,向前回溯至最近的分隔符(空格、標點)
  4. 記錄上一行長度作為下一行搜尋起點(加速收斂)

完整實作


/**
 * 智慧自動換行
 * @param source 待換行文字
 * @param font 繪製字體
 * @param maxWidth 行最大像素寬度
 * @param delimiters 分隔符字串(如 " ,.?!"),用於單詞邊界判斷
 * @return 行字串 Vector
 */
public Vector smartWrap(String source, Font font, int maxWidth, String delimiters) {
    Vector lines = new Vector();
    String text = source;
    int lastLen = 0;

    while (text.length() > 0) {
        // 1. 檢查硬性換行
        int nl = text.indexOf('\n');
        if (nl >= 0 && font.stringWidth(text.substring(0, nl)) <= maxWidth) {
            lines.addElement(text.substring(0, nl));
            text = text.substring(nl + 1);
            lastLen = 0;
            continue;
        }

        // 2. 估算搜尋範圍
        int tryLen = (lastLen > 0 && lastLen < text.length()) ? lastLen : text.length();
        int step = font.stringWidth(text.substring(0, tryLen)) > maxWidth ? -1 : 1;

        // 3. 尋找最長不超寬的切點
        while (tryLen > 0 && tryLen <= text.length()) {
            int w = font.stringWidth(text.substring(0, tryLen));
            if (w <= maxWidth && (tryLen == text.length() || 
                font.stringWidth(text.substring(0, tryLen + 1)) > maxWidth)) {
                break;  // 找到最佳長度
            }
            tryLen += step;
        }
        if (tryLen <= 0) tryLen = 1;  // 保底:至少放一字元

        // 4. 向前回溯至分隔符(避免拆分單詞)
        if (tryLen < text.length() && delimiters.length() > 0) {
            int back = tryLen;
            while (back > 0 && delimiters.indexOf(text.charAt(back - 1)) == -1) {
                back--;
            }
            if (back > 0) tryLen = back;  // 找到分隔符
        }

        // 5. 截取該行
        lines.addElement(text.substring(0, tryLen));
        lastLen = tryLen;
        text = text.substring(tryLen);
        // 跳過行首分隔符
        if (text.length() > 0 && delimiters.indexOf(text.charAt(0)) != -1) {
            text = text.substring(1);
        }
    }
    return lines;
}

調用範例


Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
g.setFont(font);
Vector lines = smartWrap(fullText, font, getWidth() - 10, " ,.?!");
for (int i = 0; i < lines.size(); i++) {
    g.drawString((String)lines.elementAt(i), 5, 5 + 20*i, Graphics.TOP | Graphics.LEFT);
}

關鍵優勢總結

特性階段一階段二階段三
修改文字便利性差(需手工切)好好
跨裝置適應性差差優(像素級)
英文/非等寬支援無無(強制拆字)完美(單詞不拆)
多語言混排無無支援

重要提醒

調用 smartWrap 時使用的 Font,必須與實際 g.drawString 時設定的 Font 完全一致,否則像素寬度計算會偏差,導致換行錯位。這是最常見的坑!

這套方案已在多款商業 J2ME 遊戲驗證,能完美支援中英日韓混排、不同螢幕解析度、動態字體大小調整,是低階 UI 文字排版的終極解法。

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