:Tips  TextView を使いこなそう 〜 表示編 〜  その3

android.text.Html#fromHtml() で <A> タグと <IMG> タグを使う

 前回前々回 に引き続いて TextView の3回目です。



 前回までの方法で大凡のテキスト表現が可能になりました。しかし、何がしかの説明文や紹介文を記述した場合にそれを説明するサイトにクリックひとつでリンク出来ればなにかと便利です。また、画像を文中に差し挟む事が出来れば複雑な数式なども LaTeX などで作成した画像を利用するなどして表示が可能になります。



<A> タグの HREF 属性で指定した URL をブラウザで開く

 単に fromHtml() に <A> タグを含めたテキストを引数として渡しても、見栄えは変わってもリンクを押しても何の変化もありません。どうしてなのでしょうか?理由は2つあります。



1. AndroidManifest.xml への設定でインターネットへのアクセス権を得る

まず1つ目の理由は、デフォルトではアプリケーションがインターネットへのアクセスを許可していないからです。しかし、事前に設定することで通信は可能になります。設定は AndroidManifest.xml で行ないます。

この一文を AndroidManifest.xml に書き加えることでアプリケーションはインターネットとの通信が可能になります。



2. TextView#setMovementMethod() に LinkMovementMethod を渡す

2つ目の理由は、Html#fromHtml() の処理過程で <A> タグが見つかると URLSpan が組み込まれます。このクラスには URLSpan#onClick(View widget) というメソッドがあり、このメソッドが呼び出されることで外部のWEBブラウザを起動し HREF 属性で設定した URL 情報がブラウザに引き渡され、その URL を元にブラウザがWEBページを表示するようプログラムされています。しかし、そのままの状態では URLSpan#onClick() を呼び出す「呼び出し役」が存在していないのです。「 TextView にあるリンクをクリックした」と URLSpan に伝える役回りを用意する必要があります。今回は LinkMovementMethod を使います。このインスタンスを TextView#setMovementMethod() の引数として渡せば onClick() の「呼び出し役」になってくれます。

サンプルコードです。

public class TextViewTest extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textview = new TextView(this);
        
        // LinkMovementMethod のインスタンスを取得します
        MovementMethod movementmethod = LinkMovementMethod.getInstance();
        
        // TextView に LinkMovementMethod を登録します
        textview.setMovementMethod(movementmethod);
        
        // <a>タグを含めたテキストを用意します
        String html = "リンクをクリックすると <a href=\"http://www.google.co.jp/\">Google</a> が表示されます";
        
        // URLSpan をテキストにを組み込みます
        CharSequence spanned = Html.fromHtml(html);
        
        textview.setText(spanned);
        setContentView(textview, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
    }
}

最初に static メソッドである getInstance() を呼び出し LinkMovementMethod 自身のインスタンスを取得します。 次に URLSpan の onClick() の「呼び出し役」として TextView に取得したインスタンスを登録します。後は、前回と同様です。特別なことはありません。

では、実際に動かしてみます。


リンクを実際にクリックしてみます。


ブラウザが起動し、無事 Google が表示されました。

以上で <A> タグが使えるようになりました。

ここで留意点が1つ。実はこのような手間をかけずともテキスト中にリンクを付ける方法があります。
TextView#setAutoLinkMask() というメソッドに Linkify.WEB_URLS という定数を引数として渡すやり方です。このやり方ですと Html#fromHtml() メソッドを使う必要もありません。文中に「 http://〜 」を見つけると自動的にリンクを張ってくれます。しかし、この設定と先ほどまで説明していた設定は共存できないようで、<A> タグで設定した方のリンクが消えてしまい実質無効になってしまいます。状況に応じて使い分ける必要がありそうです。

<IMG> タグを用いてテキスト中に画像を挿入する

 単純に <IMG> タグを用いても意味不明な画像が表示されるだけで目的の画像は表示されません。そもそも <IMG> タグの SRC 属性にどんなパスを書けばいいかも不明です。

実のところ、正解は「何を書いてもダメ」です。

SRC に何と書いてあろうと無視して「意味不明な画像」(実際はリソースの R.drawable.unknown_image )を TextView に貼り付けるようになっています。これは Html#fromHtml() のもう1つの引数違いのメソッド・・・


public static Spanned fromHtml (String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler)

が大いに関係してきます。

今まで使用してきた Html#fromHtml(String source) を使うと内部的に上記のメソッドが呼ばれ、第1引数 source 、第2引数 null 、第3引数 null が引き渡されます。第2引数に null が渡されると例の「意味不明な画像」が表示される仕組みになっています。これを回避する為には実体のある Html.ImageGetter インスタンスを明示的に渡してやらなければいけません。

Html.ImageGetter とは・・・

android.text.Html.ImageGetter

Html.ImageGetter はたった1つの抽象メソッドを持つインターフェイスです。


public abstract Drawable getDrawable (String source)

引数に SRC 属性で設定した文字列 source が引き渡されると、戻り値に描画オブジェクトの Drawable が返ってくるように実装しなければなりません。そして、この抽象メソッドを実装したクラスのインスタンスを作り、 fromHtml() の第2引数に渡せば TextView に画像が表示されるという仕組みです。

サンプルコードです。

public class TextViewTest extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textview = new TextView(this);
        
        // <img> タグ付きの文字列を作ります
        String html = "文中に画像<img src=\"icon\">が挿入されます";
        
        // 第2引数の ImageGetter は匿名インナークラスとして定義、第3引数は null
        CharSequence text = Html.fromHtml(html, new Html.ImageGetter(){
            public Drawable getDrawable(String source){
                
                // 画像のリソースIDを取得
                int id = getResources().getIdentifier(source, "drawable", getPackageName());
                
                // リソースIDから Drawable のインスタンスを取得
                Drawable d = getResources().getDrawable(id);
                
                // 取得した元画像のサイズを取得し、表示画像のサイズとする
                int w = d.getIntrinsicWidth();
                int h = d.getIntrinsicHeight()
                d.setBounds(0, 0, w, h);
                
                return d;
            }
        }, null);
        
        textview.setText(text);
        setContentView(textview, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
    }
}

最初に <IMG> タグが含まれるHTMLスタイルのテキスト用意しておきます。次に Html#fromHtml() を呼び出します。第1引数には今までどおりHTMLスタイルのテキストを、第2引数には Html.ImageGetter インターフェイスを実装したクラスのインスタンスを渡しますが、今回は便宜上、匿名インナークラスとしました。きちんと別のクラスとして定義したほうが通常は良いかもしれません。次に匿名インナークラスの getDrawable() メソッドの具体的な実装ですが、今回もまた相も変わらず画像準備の手間を惜しんでリソースにある R.drawable.icon を利用することにします。実際のケースではローカルデータなどを用いるでしょうから、ここでは「 Drawable のインスタンスを取得してるんだ」程度に理解し読み飛ばしておいてください。次に、表示画像のサイズを決めなければなりません。何もしないと縦0横0のサイズで表示されます(すなわち見えません)。サイズはケース・バイ・ケースですので必要にあわせて調整してください。そして最後に Drawable インスタンスを返してお仕舞いです。

では、実際に動かしてみます。


テキスト中に画像が挿入されました。

以上で <IMG> タグを用いてテキスト中に画像を挿入する方法の説明は終了です。



次回は・・・

未定です。

TextView の表示の続きとして Html#fromHtml() を使わずに Span を用いた方法を書こうかなぁ、とも考えてますが別の話になるかもしれません。適当にニュースネタになる可能性が一番高いかも・・・。