EditText の内容を find する

# 概要
WebView のように EditText 内をテキスト検索したいと思ったので、やり方を調べてみました。

![device-2019-01-02-195927.png](https://qiita-image-store.s3.amazonaws.com/0/102004/e473ada4-d192-21cf-f5e9-be35c887c95e.png)

具体的には、こういう感じで編集中のテキスト内を検索する機能を作りたいという動機がありました。

----

# 実装
どうやら EditText に find をする API は用意されていないようなので、String クラスの [indexOf(String str, int fromIndex)](https://developer.android.com/reference/java/lang/String.html#indexOf(java.lang.String,%20int)) と [lastIndexOf(String str, int fromIndex)](https://developer.android.com/reference/java/lang/String.html#lastIndexOf(java.lang.String,%20int)) を使ってテキストの位置を探し、EditText の [setSelection(int start, int stop)](https://developer.android.com/reference/android/widget/EditText#setSelection(int,%20int)) メソッドで選択する、という実装をする必要があるみたいです。

というわけで、以下の通り実装してみました。

```kotlin:EditTextFinder.kt
/*
 * Copyright (c) 2019 toastkidjp.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompany this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html.
 */
package jp.toastkid.yobidashi.editor

import android.widget.EditText

class EditTextFinder(private val editText: EditText) {

    private var lastIndex = 0

    fun findUp(text: String) {
        if (lastIndex <= 0) {
            lastIndex = editText.text.length
        }

        selectTextByIndex(findBackwardIndex(text), text);

        val nextBackwardIndex = findBackwardIndex(text)
        if (nextBackwardIndex == -1) {
            lastIndex = editText.text.length
        }
    }

    private fun findBackwardIndex(text: String): Int {
        val index = lastIndex - text.length - 1
        if (index < 0) {
            return -1
        }
        val haystack = editText.text.toString()
        return haystack.lastIndexOf(text, index)
    }

    fun findDown(text: String) {
        selectTextByIndex(findNextForwardIndex(text), text)
        val nextForwardIndex = findNextForwardIndex(text)
        if (nextForwardIndex == -1) {
            lastIndex = 0
        }
    }

    private fun selectTextByIndex(index: Int, text: String) {
        if (index < 0) {
            lastIndex = 0
            return
        }
        requestFocusInputArea()
        lastIndex = index + text.length
        editText.setSelection(index, lastIndex)
    }

    private fun findNextForwardIndex(text: String) {
        return editText.text.indexOf(text, lastIndex)
    }

    private fun requestFocusInputArea() {
        editText.requestFocus()
    }

}
```

## 注意
この種の実装をする場合は1つの画面に2つ以上（例：検索対象文字列を入力する EditText と、文字列を探す対象となる EditText）EditText があるはずなので、setSelection を呼ぶ前に requestFocus で検索対象の EditText にフォーカスを当てるのを忘れないようにしてください。

## 利用
あとは検索ボックスの上下ボタンの OnClickListener でそれぞれ呼び出すように処理を書けば OK です。

```kotlin:利用例
private lateinit var finder: EditTextFinder

override fun onCreate(savedInstanceState: Bundle?) {
    finder = EditTextFinder(binding.editorInput)

    backward.setOnClickListener {
        finder.findUp(text)
    }

    forward.setOnClickListener {
        finder.findDown(text)
    }
```

----
# 参考
- [EditText(Android Developers Documentation)](https://developer.android.com/reference/android/widget/EditText)