2012年1月29日日曜日

TextViewのワードラップ

AndroidのTextViewは、英単語が途中で改行されてしまわないようにワードラップ処理をしてくれます。
日本語の禁則処理もしてくれているようなのですが、どうにも不自然で納得できません。(句読点の直後に改行が入らず次の1文字も巻き込んで計算されるなど)

ソースを追うと、TextViewのプライベートメンバ mLayout で保持される StaticLayout がワードラップ処理をしているようです。(setText()に Spannable を渡すと mLayout自体は DynamicLayout になりますが、DynamicLayout が内部で StaticLayout を作ります)
うまいことワードラップする Layoutクラスを作ってリフレクションで mLayout に設定する方法もあると思うのですが、StaticLayoutはそこそこボリュームがあって手を出すのは心が折れるので、InputFilter を使う方法でワードラップ処理を改造してみました。


具体的には android.text.InputFilter のインタフェイスを実装したクラスを作り、filter()関数で改行したい場所に自分で改行文字を入れます。

    @Override
    public CharSequence filter(CharSequence source, int start, int end,
            Spanned dest, int dstart, int dend) {
        // 関連付けられたTextViewのTextPaintと幅を取得
        TextPaint paint = view.getPaint();
        int width = view.getWidth() - view.getCompoundPaddingLeft()
                - view.getCompoundPaddingRight();

        // TextView#setText()から呼ばれるだけの前提なので dest 以降の引数は使わない
        SpannableStringBuilder result = new SpannableStringBuilder();
        for (int index = start; index < end; index++) {
            // 幅チェック
            if (Layout.getDesiredWidth(source, start, index + 1, paint) > width) {
                // 行を越えた ⇒ ここまでを出力して改行を挿入
                result.append(source.subSequence(start, index));
                result.append("\n");
                start = index;

            } else if (source.charAt(index) == '\n') {
                // 改行文字 ⇒ ここまでを出力
                result.append(source.subSequence(start, index));
                start = index;
            }
        }

        if (start < end) {
            // 残りを格納(最後の行)
            result.append(source.subSequence(start, end));
        }
        return result;
    }
みたいな。
TextView#setFilters() でこのクラスのインスタンスを含む InputFilter のリストを設定してやると、TextViewが setText() 時にこのフィルタを使って文字列を調整してくれます。

TextViewの幅が確定しないと計算できないので onCreate()などで設定する場合は注意が必要です。添付サンプルでは TextView を継承したクラスで onLayout()時に setText()し直して回避しています。(もっとスマートにできるといいのですが…これだとボタンに応用するとき Buttonのサブクラスも必要になっていやだなぁ)

厳密に禁則処理をしようとすると小文字や句読点・鉤括弧などが行頭に来ないように調整する必要があって大変ですが、ボタンの表記など限られた用途であればこれで割と満足な結果が出たりします。

サンプルプロジェクト(自由に使っていただいて構いませんが無保証です)

0 件のコメント:

コメントを投稿