アプリ内の入力システムを自分で作る必要があったのでメモ兼ねて記事を書きます。
説明が足りていない点や改善点などあればコメントで遠慮なくご指摘ください。
仕様
- テンキーのように動作する
- キーを押している間は色が変わる
- 再利用可能(カスタムViewとして使い回せる)
- xmlから入力対象となるEditText(TextView)をidで指定できる
実装
アプリ全体のソースコードはこちら
今回はTableLayoutを継承したViewとして作っていきます。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumericKeyboard">
<attr name="targetField" format="reference" />
</declare-styleable>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/darker_gray"/>
<size
android:width="1dp"
android:height="1dp"/>
</shape>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#dddddd" />
</shape>
</item>
<item android:state_pressed="false">
<shape>
<solid android:color="@android:color/transparent" />
</shape>
</item>
</selector>
<resources>
...
<style name="KeyboardRow">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:gravity">center</item>
<item name="android:divider">@drawable/keyboard_divider</item>
<item name="android:showDividers">middle</item>
</style>
<style name="KeyboardButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:paddingTop">12dp</item>
<item name="android:paddingBottom">12dp</item>
<item name="android:background">@drawable/keyboard_button_background</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:gravity">center</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<TableRow style="@style/KeyboardRow">
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey7"
android:text="7" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey8"
android:text="8" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey9"
android:text="9" />
</TableRow>
<TableRow style="@style/KeyboardRow">
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey4"
android:text="4" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey5"
android:text="5" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey6"
android:text="6" />
</TableRow>
<TableRow style="@style/KeyboardRow">
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey1"
android:text="1" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey2"
android:text="2" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey3"
android:text="3" />
</TableRow>
<TableRow style="@style/KeyboardRow">
<TextView
style="@style/KeyboardButton"
android:clickable="false" />
<TextView
style="@style/KeyboardButton"
android:id="@+id/numericKey0"
android:text="0" />
<ImageView
style="@style/KeyboardButton"
android:id="@+id/numeric_key_backspace"
android:tint="@android:color/black"
app:srcCompat="@android:drawable/ic_input_delete" />
</TableRow>
</merge>
class NumericKeyboard @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : TableLayout(context, attrs), View.OnClickListener {
private val keyValues = mapOf(
R.id.numericKey0 to "0",
R.id.numericKey1 to "1",
R.id.numericKey2 to "2",
R.id.numericKey3 to "3",
R.id.numericKey4 to "4",
R.id.numericKey5 to "5",
R.id.numericKey6 to "6",
R.id.numericKey7 to "7",
R.id.numericKey8 to "8",
R.id.numericKey9 to "9"
)
private val targetFieldId: Int
private lateinit var inputConnection: InputConnection
init {
context.obtainStyledAttributes(attrs, R.styleable.NumericKeyboard).also { typedArray ->
targetFieldId = typedArray.getResourceIdOrThrow(R.styleable.NumericKeyboard_targetField)
}.recycle()
View.inflate(context, R.layout.numeric_keyboard, this).apply {
keyValues.keys.forEach { id ->
findViewById<View>(id).setOnClickListener(this@NumericKeyboard)
}
findViewById<View>(R.id.numeric_key_backspace).setOnClickListener(this@NumericKeyboard)
}
dividerDrawable = context.getDrawable(R.drawable.keyboard_divider)
showDividers = LinearLayout.SHOW_DIVIDER_BEGINNING or LinearLayout.SHOW_DIVIDER_MIDDLE
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
val targetField: TextView = (parent as View).findViewById(targetFieldId)
targetField.isFocusable = false
inputConnection = targetField.onCreateInputConnection(EditorInfo())
}
override fun onClick(v: View?) {
val view = v ?: return
if (view.id == R.id.numeric_key_backspace) {
inputConnection.deleteSurroundingText(1, 0)
} else if (view.id in keyValues.keys) {
val value = keyValues[view.id] ?: return
inputConnection.commitText(value, 1)
}
}
}
解説
今回の仕様に「xmlから入力対象となるEditText(TextView)をidで指定できる」があるので、xml側で指定できるよう追加のattrを attrs.xml
で設定しています。利用する側はこんな感じです。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context=".MainActivity">
<EditText
android:id="@+id/target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.yt8492.numerickeyboard.NumericKeyboard
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:targetField="@id/target"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
NumericKeyboard
の app:targetField="@id/target"
というようにEditTextのidを指定しています。
ここで取得したidをもとにKotlin(Java)側で InputConnection
を生成し、押されたキーに対応する値を inputConnection.commitText(value, 1)
で渡す(=EditTextに入力される)という実装にしています。
「キーを押している間は色が変わる」はキーを押しているときとそうでないときに色を変える keyboard_button_background
を各キーの background
に指定することで実現しています。