Help us understand the problem. What is going on with this article?

複数行のTextViewでImageSpanが下付きに描画される事象を回避する

More than 5 years have passed since last update.

なんかやけに苦労したのでメモしておく。

2015-03-09 01.50.56 自作ImageSpanとの比較.png

前提

拙作の TwitPane では絵文字を Html.fromHtml の img タグとして(つまり ImageSpan として)描画している。
そのTextViewの行間を広げ(150%)、複数行表示する中に絵文字が混在していると下図の通り絵文字の縦位置がズレてしまった。

2015-03-09 02.19.18s.png

具体的には1行の場合(「よろしくお願いします」の後ろの「!」)は問題ないんだけど、3行の場合は1行目と2行目にある絵文字が下付きで描画されてしまっている。

ちなみに行間の設定は textView.setLineSpacing(0, 1.5f) のように設定している。

調査とか修正とか

BitmapDrawable.setGravityが必要?

まずはBitmapDrawableの作り方(いわゆるImageGetterの実装)のほうで調べてみたんだけど、
この記事で言及されているように setGravity(Gravity.TOP) を指定したが改善しなかった。

ALIGN_BASELINE を指定してみる

次に ImageSpan をよく調べるとコンストラクタに ImageSpan.ALIGN_BASELINE が指定できるようなので、(残念ながら Html.fromHtml からはどう頑張っても指定できないんだけど、ともかく試しに) ALIGN_BASELINE にしてみたのが下図の青と緑の四角。

画像1.png

確かに1行の場合は BASELINE にあわせて描画されてるんだけど、依然として複数行の1行目は下付きっぽいまま(若干上にズレたけど)。

ImageSpan(<DynamicDrawableSpan) の描画部分を置き換える

ImageSpan の実際の描画処理はスーパークラスの DynamicDrawableSpan が行っているのでその draw メソッドを確認。

DynamicDrawableSpan.java
    @Override
    public void draw(Canvas canvas, CharSequence text,
                     int start, int end, float x, 
                     int top, int y, int bottom, Paint paint) {
        Drawable b = getCachedDrawable();
        canvas.save();

        int transY = bottom - b.getBounds().bottom;
        if (mVerticalAlignment == ALIGN_BASELINE) {
            transY -= paint.getFontMetricsInt().descent;
        }

        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }

描画エリアのY方向の設定を transY で行ってるんだけど、これを引数の bottom を基準に設定している。
これが微妙に間違ってるようで、y を基準にする必要があるみたい。

ありがたいことに(当たり前だけど)drawメソッドがpublicなのでこれを置き換える感じで ImageSpan を自作して修正してみた。

EmojiImageSpan.java
class EmojiImageSpan extends ImageSpan {
    public EmojiImageSpan(Drawable d, String src) {
        super(d, src);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text,
                     int start, int end, float x,
                     int top, int y, int bottom, Paint paint) {
        Drawable b = getCachedDrawable();
        canvas.save();

//        int transY = bottom - b.getBounds().bottom;
//        transY -= paint.getFontMetricsInt().descent;
        int transY = y - b.getBounds().bottom;
        transY += paint.getFontMetricsInt().descent;

        canvas.translate(x, transY);
        b.draw(canvas);
        canvas.restore();
    }

    private Drawable getCachedDrawable() {
        WeakReference<Drawable> wr = mDrawableRef;
        Drawable d = null;


        if (wr != null)
            d = wr.get();

        if (d == null) {
            d = getDrawable();
            mDrawableRef = new WeakReference<Drawable>(d);
        }

        return d;
    }

    private WeakReference<Drawable> mDrawableRef;
}

すると下図の青四角の通り、いい感じの高さに描画できるようになった。

画像4.png

というわけで Html.fromHtml は諦めて、絵文字部分を自作ImageSpanに差し替えることでこの事象は回避できるようです。

補足:絵文字の正しい描画位置

Twitter絵文字の描画は https://github.com/twitter/twemoji#inline-styles にあるように上方向に -10% ずらす必要がある(あと左側に5%、右側に10%のマージンも)。

img.emoji {
   height: 1em;
   width: 1em;
   margin: 0 .05em 0 .1em;
   vertical-align: -0.1em;
}

これらも踏まえて位置を調整してあげる必要がありますね。

  • 修正前
    2015-03-09 02.19.18s.png

  • 修正後
    2015-03-09 06.28.57s.png

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした