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

J2ME 低級 UI 文本自動換行算法:支援中英混排與單詞不拆分

     編輯:J2ME

前言:Canvas 繪圖缺乏文本佈局能力的痛點

MIDP Graphics.drawString() 僅支持單行繪製,RPG 對話框、任務日誌、幫助文檔等多行文本場景必須自行實現換行邏輯。本文提供一個生產級 getSubsection() 方法,完美處理中英混排、單詞完整性、顯式換行/縮進、指定像素寬度約束。

核心算法實現


import java.util.Vector;
import javax.microedition.lcdui.Font;

/**
 * 將文本按指定像素寬度分行
 * @param strSource 待分行文本
 * @param font 繪製字體
 * @param width 最大行寬(像素)
 * @param strSplit 斷詞分隔符(英文模式用 " ,.?!",中文模式傳 "")
 * @return Vector 分行結果
 */
public Vector getSubsection(String strSource, Font font, int width, String strSplit) {
    Vector lines = new Vector();
    String temp = strSource;
    int lastLen = 1; // 上次分行長度,作為下次搜索起點優化

    try {
        while (temp.length() > 0) {
            // 1. 檢查顯式換行符 \n
            int nlPos = temp.indexOf('\n');
            if (nlPos > 0) {
                // 若 \n 前內容未超寬,直接按 \n 分行
                if (font.stringWidth(temp.substring(0, nlPos)) <= width) {
                    lines.addElement(temp.substring(0, nlPos));
                    temp = temp.substring(nlPos + 1);
                    lastLen = 1;
                    continue;
                }
                // 否則忽略 \n,按寬度強制分行
                nlPos = -1;
            }

            int cutPos;
            if (nlPos == -1) { // 無顯式換行或 \n 被忽略
                // 2. 二分/線性搜索找到最大不超寬位置
                if (lastLen > temp.length()) {
                    cutPos = temp.length(); // 剩餘長度小於上次,直接取全
                } else {
                    cutPos = lastLen;
                    int step = font.stringWidth(temp.substring(0, cutPos)) > width ? -1 : 1;
                    // 尋找臨界點:[0,cutPos] 寬度 ≤ width 且 [0,cutPos+1] > width
                    while (cutPos < temp.length() &&
                           !(font.stringWidth(temp.substring(0, cutPos)) <= width &&
                             font.stringWidth(temp.substring(0, cutPos + 1)) > width)) {
                        cutPos += step;
                        if (cutPos == temp.length()) break;
                    }
                }

                // 3. 英文單詞不拆分邏輯
                if (strSplit.length() > 0 && cutPos < temp.length()) {
                    int origCut = cutPos;
                    // 向前回溯找分隔符
                    while (cutPos > 0 && strSplit.indexOf(temp.charAt(cutPos - 1)) == -1) {
                        cutPos--;
                    }
                    // 回溯到行首仍無分隔符(單詞超長),恢復強制切分
                    if (cutPos == 0) cutPos = origCut;
                }
            } else {
                cutPos = nlPos; // 使用 \n 位置
            }

            // 4. 記錄分行結果
            lastLen = cutPos;
            lines.addElement(temp.substring(0, cutPos));

            // 5. 處理剩餘文本
            if (cutPos == temp.length()) {
                temp = "";
            } else {
                temp = temp.substring(cutPos);
                // 跳過殘留的 \n
                if (temp.length() > 0 && temp.charAt(0) == '\n') {
                    temp = temp.substring(1);
                }
            }
        }
    } catch (Exception e) {
        System.out.println("getSubsection error: " + e);
    }
    return lines;
}

算法關鍵點解析

實戰調用示例


// 遊戲對話框渲染
private int lineHeight = 15;      // 行高
private int scrollIndex = 0;      // 滾動偏移(按鍵控制)

public void renderDialog(Graphics g, String text) {
    Vector lines = getSubsection(text, Font.getDefaultFont(), getWidth(), " ,.?!:;,。?");
    
    for (int i = scrollIndex; i < lines.size(); i++) {
        int y = (i - scrollIndex) * lineHeight;
        if (y > getHeight()) break; // 超出屏幕停止
        g.drawString((String) lines.elementAt(i), 0, y, Graphics.TOP | Graphics.LEFT);
    }
    lines = null; // 釋放引用
}

擴展建議

  1. 富文本標記支持:擴展解析 [color=FF0000]紅字[/color] 等標記,生成 LineSegment{text, color, font} 結構
  2. 雙向文本支持:阿拉伯語/希伯來語 RTL 佈局需集成 Bidi 算法
  3. 緩存機制:相同文本+字體+寬度 → 緩存分行結果,避免重複計算
  4. 行間距/對齊:增加 leading、alignment 參數

日文換行特殊說明

日文混合漢字(漢字)、平假名、片假名、羅馬字,換行規則極其複雜(禁則處理:行首禁用「)」等、行尾禁用「(」等)。建議:

  • 簡易方案:視為中文模式(strSplit="")逐字換行,接受部分不合規
  • 專業方案:集成 Budoux 或 LineBreaker 庫(需 JAR 體積預算)

此算法已在多款商業 RPG 手遊中穩定運行,是 Canvas 文本渲染的基石組件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
特性實現細節代碼位置
像素精確換行使用 font.stringWidth() 實測寬度,非字符計數搜索循環條件判斷
英文單詞保護strSplit 指定分隔符,向前回溯至最近分隔符第 3 步回溯邏輯
中日韓逐字換行strSplit="" 禁用回溯,直接按像素切分參數判斷分支
顯式換行 \n優先識別,若未超寬則強制換行第 1 步 nlPos 處理
縮進 \t 支持調用方預處理 \t → " " (6空格) 或擴展算法預處理/擴展點
性能優化lastLen 記憶上次分行長度,作為下次搜索起點變量初始化與更新