search
LoginSignup
34

More than 3 years have passed since last update.

posted at

updated at

Organization

AndroidでテンキーのようなViewを作る

アプリ内の入力システムを自分で作る必要があったのでメモ兼ねて記事を書きます。
説明が足りていない点や改善点などあればコメントで遠慮なくご指摘ください。

仕様

  • テンキーのように動作する
  • キーを押している間は色が変わる
  • 再利用可能(カスタムViewとして使い回せる)
  • xmlから入力対象となるEditText(TextView)をidで指定できる

実装

アプリ全体のソースコードはこちら
今回はTableLayoutを継承したViewとして作っていきます。

values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="NumericKeyboard">
        <attr name="targetField" format="reference" />
    </declare-styleable>
</resources>
drawable/keyboard_divider.xml
<?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>
drawable/keyboard_button_background.xml
<?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>
values/styles.xml
<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>

layout/numeric_keyboard.xml
<?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>
NumericKeyboard.kt
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 で設定しています。利用する側はこんな感じです。

activity_main.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>

NumericKeyboardapp:targetField="@id/target" というようにEditTextのidを指定しています。
ここで取得したidをもとにKotlin(Java)側で InputConnection を生成し、押されたキーに対応する値を inputConnection.commitText(value, 1) で渡す(=EditTextに入力される)という実装にしています。

「キーを押している間は色が変わる」はキーを押しているときとそうでないときに色を変える keyboard_button_background を各キーの background に指定することで実現しています。

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
What you can do with signing up
34