概要
Androidアプリで、テキスト入力欄(EditText
)のテキストの途中に画像を挿入したい。
また、挿入した画像をクリックしたときに何か(例えばビューアを開くなど)できるようにしたい。
実装方法を調査した。
調査・検証したこと
テキスト間に画像を挿入する方法
Spannable
を使って、テキスト間に画像を挿入することができる。
-
Spanned
マークアップオブジェクトを持つテキストのインターフェース
すべてのtext
のクラスに変更可能なマークアップやテキストがあるわけではない -
Spannable
マークアップオブジェクトを付加したり分離したりできるテキストのインターフェース
すべてのSpannable
のクラスに変更可能なテキストがあるわけではない -
Editable
内容やマークアップを変更できるテキストのインターフェース -
SpannableString
内容は変更不可であるがマークアップオブジェクトを付加したり分離したりできるテキストのクラス -
SpannableStringBuilder
内容やマークアップを変更できるテキストのクラス
画像マークアップはImageSpan
である。
リファレンスに記載されている使用例を以下に示す。
val string = SpannableString("Bottom: span.\nBaseline: span.")
// using the default alignment: ALIGN_BOTTOM
string.setSpan(
what = ImageSpan(context = this, resourceId = R.mipmap.ic_launcher),
start = 7,
end = 8,
flags = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
string.setSpan(
ImageSpan(
context = this,
resourceId = R.mipmap.ic_launcher,
verticalAlignment = DynamicDrawableSpan.ALIGN_BASELINE
),
22,
23,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
スペースのあたりに画像が挿入される(スペースが置換される)。
// イメージ
Bottom:😊span.
Baseline:😊span.
以下の画像リソースをImageSpan
にできる。
EditText
のテキストに画像を挿入する
固定の文字列に画像を挿入する方法は判明したため、同様の方法でEditText
のテキスト間に画像を挿入してみる。
EditText
のtextはEditable
であるためsetSpan
等が可能である。
以下のようなコードで、カーソルの位置に画像を挿入できる……
val cursorEnd = editText.getSelectionEnd() // カーソルの終端が何文字目の位置にあるか
editText.text.insert(cursorEnd, " ") // ImageSpanで置換するための文字列を挿入する
editText.text.setSpan(ImageSpan(drawable), cursorEnd, cursorEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
……と考えたが、これだけでは「何かが挿入されているっぽいけど何も見えない」状態になる。
表示するときの画像の四隅の位置を明示的に設定する必要がある。
drawable.setBounds(
left = 0,
top = 0,
right = drawable.getIntrinsicWidth(),
bottom = drawable.getIntrinsicHeight()
)
getIntrinsic*()
でdrawable
の幅・高さを取得している。
このdrawable
をSpanのリソースにすることで、カーソルの位置にdrawable
が表示される(insert
で挿入したスペースが置換される)。
カーソルの位置
上記の例では、カーソルの位置としてgetSelectionEnd()
で取得した値を用いている。
getSelectionStart()
もあり、これはカーソルの開始位置が取得できる。
getSelectionStart()
とgetSelectionEnd()
の値は基本的に一致するが、範囲選択をした場合には異なる値になる。
ImageSpan
にクリックイベントを付ける
画像をクリック(タップ)したときに何かできるようにしたい。
ClickableSpan
というSpanを使う。
ImageSpan
と同じ位置にClickableSpan
をセットすると、画像をクリックしたときにClickableSpan
のonClick
が発火するようになる。
editText.text.setSpan(object : ClickableSpan() {
override fun onClick(view: View) {
// クリック後に実行することを書く
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
}
}, cursorEnd, cursorEnd + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
その他
行間を調整する
ImageSpan
はデフォルトでは下揃えで表示される。このとき、行間が広いと、顕著なClickableSpan
とのズレが発生する。
textの行間指定attrは割合(lineSpacingMultiplier
)と固定値(lineSpacingExtra
)の2種類あり、割合で指定すると画像の高さに応じて行間が広くなってしまう。そこで、行間を固定値で指定すれば、画像の大きさに依らず行間を一定にできる。
<EditText
android:lineSpacingMultiplier="1.03" />
<EditText
android:lineSpacingExtra="3dp" />
例えば画像の高さを1000dpとすると、lineSpacingMultiplier
では行間が30dpになってしまうが、lineSpacingExtra
であれば3dp固定にできる。
画像の右側をクリックしたときの挙動
たとえば、画像のすぐ右側にカーソルを持って行こうとして画像の右側をクリックすると、ClickableSpan
が反応してイベントが発生してしまう。
画像の右側に何か文字があれば回避できるため、画像とあわせて半角スペース等も挿入すると良さそうである。
挿入した画像を取得する
getSpans()
で挿入されているSpan一覧を取得できる。Spanの種類(クラス)を指定することもできる。
spannableString.getSpans(0, spannableString.length - 1, ImageSpan::class.java) // spannableString全体からImageSpanを取得する
spannableString.getSpanStart(span)
でspan
の開始位置を、span.getDrawable()
でspan
の画像(Drawable
)を取得できる。
まとめ
-
ImageSpan
をEditText
に挿入するときは画像(Drawable
)にsetBounds
する -
ImageSpan
と同じ位置にClickableSpan
を挿入すると、画像タップでイベントを発火させられるようになる