Android で文字列選択時に表示される Menu に任意の Item を追加する

developer.android.com

MainActivity.kt

import android.os.Bundle
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main);

        val mainTextView = findViewById<TextView>(R.id.main_text)
        // Layout XML で android:textIsSelectable="true" しても OK
        mainTextView.setTextIsSelectable(true)
        // Action Mode をカスタマイズするときにこのメソッドを呼び出す
        // https://developer.android.com/reference/android/widget/TextView?authuser=1#setCustomSelectionActionModeCallback(android.view.ActionMode.Callback)
        mainTextView.customSelectionActionModeCallback = object : ActionMode.Callback {
            override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                // 追加で表示したいメニューは Menu resource で定義して inflate する
                mode?.menuInflater?.inflate(R.menu.my_text_view_menu, menu)
                return true
            }

            override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                // Action Mode の作成後と無効化後に任意の処理があればここで行う
                return false
            }

            override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
                // item のタップをここでハンドリングして任意の処理をする
                // ここで何か処理をしたのなら終了でよいので mode?.finish() で閉じて、true を返す
                return when (item?.itemId) {
                    R.id.action_nyandafull -> {
                        showToastSelectedText()
                        mode?.finish()
                        true
                    }
                    // なにも処理しなかったので false を返して Android に処理を継続させる
                    else -> false
                }
            }

            override fun onDestroyActionMode(mode: ActionMode?) {
                // Action Mode を終了したときに任意の処理があればここで行う
            }
        }
    }

    /**
     * TestView.text の選択された文字列を Toast で表示する
     */
    private fun showToastSelectedText() {
        val mainTextView = findViewById<TextView>(R.id.main_text)
        val selectedText =
            mainTextView.text.subSequence(mainTextView.selectionStart, mainTextView.selectionEnd)

        Toast.makeText(this, selectedText, Toast.LENGTH_SHORT).show()
    }
}

my_text_view_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/action_nyandafull"
        android:title="にゃんだふる" />
</menu>

動作イメージ

f:id:bps_tomoya:20210529210236p:plain:w300

メモ

  • 既存の Item は Menu.removeItem(int) で一部削除できる
  • 任意の場所(たとえば先頭)に Menu を差し込む手段が見つからなかった
  • 一定文字数を超えてしまうとネストされてしまう
    • 今回のケースでは日本語10文字でネストされてしまった

f:id:bps_tomoya:20210529205556p:plain:w300 f:id:bps_tomoya:20210529205613p:plain:w300