5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Androidアプリのテキスト入力に画像を挿入して更にタップしたい

Posted at

概要

Androidアプリで、テキスト入力欄(EditText)のテキストの途中に画像を挿入したい。

このような感じ
add_image_succeeded.png

また、挿入した画像をクリックしたときに何か(例えばビューアを開くなど)できるようにしたい。
実装方法を調査した。

調査・検証したこと

テキスト間に画像を挿入する方法

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)

……と考えたが、これだけでは「何かが挿入されているっぽいけど何も見えない」状態になる。
unable_add_image.png

表示するときの画像の四隅の位置を明示的に設定する必要がある。

drawable.setBounds(
 left = 0,
 top = 0,
 right = drawable.getIntrinsicWidth(),
 bottom = drawable.getIntrinsicHeight()
)

getIntrinsic*()drawableの幅・高さを取得している。
このdrawableをSpanのリソースにすることで、カーソルの位置にdrawableが表示される(insertで挿入したスペースが置換される)。
add_image_succeeded.png

カーソルの位置

上記の例では、カーソルの位置としてgetSelectionEnd()で取得した値を用いている。
getSelectionStart()もあり、これはカーソルの開始位置が取得できる。
getSelectionStart()getSelectionEnd()の値は基本的に一致するが、範囲選択をした場合には異なる値になる。

ImageSpanにクリックイベントを付ける

画像をクリック(タップ)したときに何かできるようにしたい。
ClickableSpanというSpanを使う。
ImageSpanと同じ位置にClickableSpanをセットすると、画像をクリックしたときにClickableSpanonClickが発火するようになる。

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)

タップ中
tapping_image.png

その他

行間を調整する

ImageSpanはデフォルトでは下揃えで表示される。このとき、行間が広いと、顕著なClickableSpanとのズレが発生する。
textの行間指定attrは割合(lineSpacingMultiplier)と固定値(lineSpacingExtra)の2種類あり、割合で指定すると画像の高さに応じて行間が広くなってしまう。そこで、行間を固定値で指定すれば、画像の大きさに依らず行間を一定にできる。

<EditText
    android:lineSpacingMultiplier="1.03" />
<EditText
    android:lineSpacingExtra="3dp" />

例えば画像の高さを1000dpとすると、lineSpacingMultiplierでは行間が30dpになってしまうが、lineSpacingExtraであれば3dp固定にできる。

画像の右側をクリックしたときの挙動

たとえば、画像のすぐ右側にカーソルを持って行こうとして画像の右側をクリックすると、ClickableSpanが反応してイベントが発生してしまう。
tap_right_of_image.png

画像の右側に何か文字があれば回避できるため、画像とあわせて半角スペース等も挿入すると良さそうである。

挿入した画像を取得する

getSpans()で挿入されているSpan一覧を取得できる。Spanの種類(クラス)を指定することもできる。

spannableString.getSpans(0, spannableString.length - 1, ImageSpan::class.java) // spannableString全体からImageSpanを取得する

spannableString.getSpanStart(span)spanの開始位置を、span.getDrawable()spanの画像(Drawable)を取得できる。

まとめ

  • ImageSpanEditTextに挿入するときは画像(Drawable)にsetBoundsする
  • ImageSpanと同じ位置にClickableSpanを挿入すると、画像タップでイベントを発火させられるようになる
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?