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

Java ME 繪製自動換行文字完整實作

     編輯:Java 相關知識

Java ME Canvas/GameCanvas 文字自動換行繪製指南

Java ME (Micro Edition) 環境下,Graphics.drawString() 不支援自動換行。本文提供完整的文字換行演算法與繪製實作,適用於行動裝置、嵌入式裝置的有限螢幕空間。

核心挑戰

  • 螢幕寬度固定 (如 240px、320px)
  • 字型大小固定,字元寬度不一 (比例字型)
  • 無標準庫支援,需自行實作斷行邏輯
  • 需處理中英混排、標點禁則、長單字截斷

文字換行核心演算法

public class TextWrapper {
    /**
     * 將文字按指定寬度進行換行
     * @param g Graphics 物件 (用於測量字元寬度)
     * @param text 原始文字
     * @param maxWidth 最大像素寬度
     * @param font 字型
     * @return 換行後的字串陣列,每個元素為一行
     */
    public static String[] wrapText(Graphics g, String text, int maxWidth, Font font) {
        g.setFont(font);
        List lines = new ArrayList<>();
        
        String[] paragraphs = text.split("\n"); // 先按明確換行分段
        for (String para : paragraphs) {
            if (para.isEmpty()) {
                lines.add("");
                continue;
            }
            
            StringBuilder currentLine = new StringBuilder();
            char[] chars = para.toCharArray();
            
            for (int i = 0; i < chars.length; i++) {
                char c = chars[i];
                String testLine = currentLine.append(c).toString();
                int width = g.getFont().stringWidth(testLine);
                
                if (width > maxWidth) {
                    // 當前字元導致超寬,需換行
                    currentLine.deleteCharAt(currentLine.length() - 1); // 移除最後字元
                    String line = currentLine.toString().trim();
                    lines.add(line.isEmpty() ? String.valueOf(c) : line);
                    currentLine = new StringBuilder(String.valueOf(c)); // 新行從當前字元開始
                }
            }
            
            // 加入最後一行
            if (currentLine.length() > 0) {
                lines.add(currentLine.toString());
            }
        }
        
        return lines.toArray(new String[0]);
    }
}

進階:禁則處理 (避免行首出現標點、行尾殘留開頭標點)

public class AdvancedTextWrapper {
    private static final Set LINE_START_FORBIDDEN = Set.of(
        ',', '。', '、', '?', '!', ';', ':', '」', '』', '】', '〕', '》',
        ',', '.', '?', '!', ';', ':', ')', ']', '}'
    );
    
    private static final Set LINE_END_FORBIDDEN = Set.of(
        '「', '『', '【', '〔', '《', '<', '(', '[', '{', '「', '『'
    );
    
    public static String[] wrapWithKinsoku(Graphics g, String text, int maxWidth, Font font) {
        List rawLines = Arrays.asList(wrapText(g, text, maxWidth, font));
        List adjusted = new ArrayList<>();
        
        for (String line : rawLines) {
            String processed = adjustLineStart(line);
            processed = adjustLineEnd(processed, adjusted);
            adjusted.add(processed);
        }
        return adjusted.toArray(new String[0]);
    }
    
    private static String adjustLineStart(String line) {
        // 行首禁則:若行首為禁用字元,嘗試從上一行移一字元過來
        // 實作需配合上一行處理,此處簡化示意
        return line;
    }
    
    private static String adjustLineEnd(String line, List previousLines) {
        // 行尾禁則:若行尾為開頭禁用字元,推到下一行
        if (!line.isEmpty() && LINE_START_FORBIDDEN.contains(line.charAt(line.length() - 1))) {
            if (!previousLines.isEmpty()) {
                String lastLine = previousLines.remove(previousLines.size() - 1);
                previousLines.add(lastLine + line.charAt(line.length() - 1));
                return line.substring(0, line.length() - 1);
            }
        }
        return line;
    }
}

完整繪製元件:TextBoxComponent

public class TextBoxComponent {
    private final Font font;
    private final int x, y, width, height;
    private final int lineSpacing;
    private String[] wrappedLines;
    private int visibleStartLine = 0; // 捲動支援
    
    public TextBoxComponent(int x, int y, int width, int height, Font font) {
        this.x = x; this.y = y; this.width = width; this.height = height;
        this.font = font;
        this.lineSpacing = font.getHeight() + 2; // 行高 + 間距
    }
    
    public void setText(Graphics g, String text) {
        this.wrappedLines = TextWrapper.wrapText(g, text, width, font);
        this.visibleStartLine = 0;
    }
    
    public void paint(Graphics g) {
        if (wrappedLines == null) return;
        
        g.setFont(font);
        g.setColor(0x000000); // 黑色文字
        
        int maxVisibleLines = height / lineSpacing;
        int endLine = Math.min(wrappedLines.length, visibleStartLine + maxVisibleLines);
        
        for (int i = visibleStartLine; i < endLine; i++) {
            int lineY = y + (i - visibleStartLine) * lineSpacing;
            g.drawString(wrappedLines[i], x, lineY, Graphics.TOP | Graphics.LEFT);
        }
    }
    
    // 捲動控制
    public void scrollUp() {
        if (visibleStartLine > 0) visibleStartLine--;
    }
    
    public void scrollDown() {
        int maxVisibleLines = height / lineSpacing;
        if (visibleStartLine + maxVisibleLines < wrappedLines.length) {
            visibleStartLine++;
        }
    }
    
    public boolean canScrollUp() { return visibleStartLine > 0; }
    public boolean canScrollDown() { 
        int maxVisibleLines = height / lineSpacing;
        return visibleStartLine + maxVisibleLines < wrappedLines.length; 
    }
}

在 Canvas/GameCanvas 中使用

public class MyCanvas extends GameCanvas implements Runnable {
    private TextBoxComponent textBox;
    private String longText = "這是一段很長的測試文字,用來驗證 Java ME 環境下的自動換行繪製功能。"
                            + "包含中文、English、數字123、標點符號,以及很長很長的英文單詞 "
                            + "supercalifragilisticexpialidocious 會怎麼處理呢?";
    
    public MyCanvas() {
        super(false);
        Font font = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_MEDIUM);
        textBox = new TextBoxComponent(10, 10, getWidth() - 20, getHeight() - 20, font);
    }
    
    public void start() {
        Graphics g = getGraphics();
        textBox.setText(g, longText);
        new Thread(this).start();
    }
    
    public void run() {
        while (true) {
            // 處理按鍵捲動
            int key = getKeyStates();
            if ((key & UP_PRESSED) != 0) textBox.scrollUp();
            if ((key & DOWN_PRESSED) != 0) textBox.scrollDown();
            
            // 重繪
            Graphics g = getGraphics();
            g.setColor(0xFFFFFF); // 白底
            g.fillRect(0, 0, getWidth(), getHeight());
            textBox.paint(g);
            flushGraphics();
            
            try { Thread.sleep(50); } catch (InterruptedException ignored) {}
        }
    }
}

效能優化建議

優化點說明
快取換行結果文字不變時避免重複計算,只在 setText() 時重算
預測量字元寬度建立 char[] 寬度查找表,避免重複呼叫 stringWidth()
只繪製可見區域配合 visibleStartLine 只畫螢幕內的行
使用 StringBuilder避免字串拼接產生大量暫時物件
釋放參考不再需要的 wrappedLines 設為 null 協助 GC

常見問題對策

⚠️ 單字過長超過螢幕寬度

強制斷字:在演算法中檢測單字長度 > maxWidth 時,按字元強制斷行,或以連字號 "-" 斷開。

✅ 中英混排對齊

使用等寬字型 (Font.FACE_MONOSPACE) 可簡化對齊,但比例字型 (FACE_PROPORTIONAL) 閱讀體驗更好,需接受字元寬度不一的複雜度。


Java ME 文字自動換行雖需自行實作,但掌握核心演算法 (逐字測量、寬度判斷、禁則處理) 並封裝為可復用元件,即可在資源受限裝置上呈現優質閱讀體驗。關鍵在於:快取計算結果、只繪製可見區域、善用 StringBuilder。

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