Androidアプリでソフトウェアキーボードの入力を1文字ずつ検出する

# 概要
Androidで1文字ずつ文字の検出をしようと思ってonKey~をつかってもできませんでした．
ちょっと調べたところ，ソフトウェアキーボードのキーボードイベントを拾ってくれないらしいです．
https://developer.android.com/training/keyboard-input/commands

ので，1文字ずつ文字の検出する機能を実装しました．

EditTextの場合，TextChangedListenerを使えば一応できますが，入力ではなくてEditTextのテキストが変わったら反応するため処理がめんどくさいので使いません．

## ソースコード
https://github.com/yokoro13/Get-Software-KeyBoard-Input

# 実装
今回はEditTextの場合で実装していきます．
この実装方法だと（たぶん）ほかのViewでもつかえます．WebViewでは使えました．

やることは簡単で，もとになるViewを継承して`onCreateInputConnection`と`dispatchKeyEvent`をオーバーライトするだけです．

`dispatchKeyEvent`で`KeyEvent`を拾えるので,そこで入力時にしたいことを記述します．

```kotlin:MyEditText.kt
package com.usr.myedittexttest

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.KeyEvent
import android.view.inputmethod.BaseInputConnection
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.EditText

class MyEditText : EditText {

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        return BaseInputConnection(this, false)
    }

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        val dispatchFirst = super.dispatchKeyEvent(event)

        if (event.action == KeyEvent.ACTION_UP){
            val charCode = event.unicodeChar
            // 入力時の処理
            Log.d("MyEditText", "onKey keyCode=$charCode")
            Log.d("MyEditText", "onKey input=${charCode.toChar()}")
        }
        return dispatchFirst
    }
}
```

新しいViewはもとのViewと同じように使用できます．

```kotlin:MainActivity
package com.usr.myedittexttest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {
    lateinit var myEditText: MyEditText

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

        myEditText = findViewById(R.id.my_edit)
    }
}
```

`activity_main.xml`は使うViewを作成したものに変更します．

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.usr.myedittexttest.MyEditText
        android:id="@+id/my_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

# 入力を通知するListenerを作成する場合
入力したことを通知するListenerを使ったほうが便利なのでListenerを作成します.

まずListenerを作成

```kotlin:InputListener
package com.usr.myedittexttest

import java.util.*

interface InputListener: EventListener {
    fun onKey(text: Char)
}
```

次に`MyEditText`を少し改造

```kotlin:MyEditText
package com.usr.myedittexttest

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.KeyEvent
import android.view.inputmethod.BaseInputConnection
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.EditText

class MyEditText : EditText {

    private var listener: InputListener? = null

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        return BaseInputConnection(this, false)
    }

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        val dispatchFirst = super.dispatchKeyEvent(event)

        if (event.action == KeyEvent.ACTION_UP){
            val charCode = event.unicodeChar
            // 入力時の処理
            Log.d("MyEditText", "onKey keyCode=$charCode")
            Log.d("MyEditText", "onKey input=${charCode.toChar()}")
            // 入力を通知
            listener?.onKey(charCode.toChar())
        }
        return dispatchFirst
    }

    fun setListener(listener: InputListener){
        this.listener = listener
    }
}
```

`MainActivity`では入力の通知を受け取った時の処理を記述します．
そのためにInputListenerを実装します．
通知を受け取ると`onKey`が呼ばれるので，そこに処理を記述してください．

```kotlin:MainActivity.kt
package com.usr.myedittexttest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

class MainActivity : AppCompatActivity(), InputListener{
    lateinit var myEditText: MyEditText

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

        myEditText = findViewById(R.id.my_edit)
        myEditText.setListener(this)
    }

    override fun onKey(text: Char) {
        Log.d("MainActivity", "text=$text")
        // TODO something
    }
}
```

# 終わりに
勉強し始めたばかりですので，より良い方法があるかと思います．よろしければお教えくださいますと幸いです．