カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit2 – 1 第5回:View Binding の使い方

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit2 – レイアウトUnit2-1 第5回 の内容をまとめたものです(翻訳ではありません)


Unit2 – 1 第5回 のポイント

  • KotlinコードからUI部品に設定した値を参照したり、修正したりする方法
  • findViewById() のかわりに View Binding を使う方法
  • KotlinでDouble型を使って小数の計算をします
  • 数値を通貨の形に直して表示する方法
  • Stinrg型(文字列型)の使い方
  • Android Studio の Logcat 機能の使い方(アプリの問題を見つけるのに使う)

プロジェクトに含まれるファイルの確認

Android Studio のプロジェクトに含まれるファイルを確認しておきますね

  • 前回UIを作成したプロジェクトを開いておきます
  • 使用するファイルは Project(ウィンドウの左)から開くことができます。Project の表示は Android にしておきます

重要なフォルダはこの3つですね

  • Kotlinファイル(たとえばMainActivity.kt)は java フォルダ以下にあります(Kotlinコードも java フォルダで管理されています)
  • アプリのリソース(たとえば activity_main.xml , strings.xml など)は res フォルダ以下にあります
  • Gladle(Android Studio で使われているアプリのビルドを自動化するツール)関連のファイル(build.gradle など)はGradle Scripts 以下にあります
    • アプリのコードやリソースを変更すると、Gradle はそれを検知してちゃんとビルドできるように自動で再設定します
    • アプリをエミュレータや実機にインストールして実行をコントロールするのも Gradle が行っています

View Binding を設定する

これからは View Binding ですね!

  • KotlinコードからUI部品にアクセスするのに今までは findViewById() メソッドを使っていました
  • findViewById() は使用するUI部品ごとに呼び出さないといけないのでUI部品が増えてくるとどうしてもゴチャゴチャしてしまいます
  • そこでこれからは View Binding を使うようにします
  • 最初に View Binding を使えるようにしておけば、findViewById() よりも速く簡単にUI部品にアクセスできます
  • View Binding を使うには、まずプロジェクト毎に Gradle への設定が必要です
    • モジュールの build.gradle を開きます(Project から Gradle Scripts > build.gradle (Module:~) をダブルクリック )
    • ファイルの android セクションに次の設定を追加します(Android Studio 4.0以降)
android {
    buildFeatures {
        viewBinding = true
    }
}
  • build.gradle の書き換えをするとウィンドウの上の方にメッセージが出て「Sync Now」というリンクが表示されます。これをクリックします
  • ウィンドウの下の方に「Gradle sync finished」と出れば作業完了です。プロジェクトで View Binding が使えるようになったので build.gradle は閉じて構いません

View Binding を使う

Kotlin で View Binding を使うには、必ず最初に次のようなコードを書いておきます

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}
  • setContentView() では UI部品(View)の階層の root を指定します
  • これで findViewById() を使わなくても binding オブジェクトですべてのUI部品(View)を参照できるようになります
  • UI部品(View)ごとに参照するための変数をひとつひとつ用意する必要もありません。bindingオブジェクトを使って直接呼び出して構いません

findViewById() を使うとこんな感じですね

// Old way with findViewById()
val myButton: Button = findViewById(R.id.my_button)
myButton.text = "A button"

View Binding で置き換えて少しすっきり

// Better way with view binding
val myButton: Button = binding.myButton
myButton.text = "A button"

とてもすっきり!

// Best way with view binding and no extra variable
binding.myButton.text = "A button"

チップの金額を計算するアプリを作ってみる

UI部品の情報をKotlinコードから参照して計算を行うアプリを作ります

ボタンに click listener を設定する

ユーザがボタンをタップしたことを検出します。click listener という機能を使いますね

binding.calculateButton.setOnClickListener{ calculateTip() } 
  • このコードを MainActivity.kt の onCreate() メソッドの中に記述します
  • ボタンがタップされたことを検知するのに click listener 機能を追加します。setOnClickListener() メソッドを使います
  • タップが検知されたら calculateTip() という関数を呼び出すように指定しています
  • とりあえず空の calculateTip() を宣言しておくと次のようになります
class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.calculateButton.setOnClickListener{ calculateTip() } 
    }

    fun calculateTip() {

    }
}

calculateTip() メソッドを作る

EditText(UI部品)に記入された金額を小数として読み込みます

fun calculateTip() {
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
}
  • EditText に入力されている内容を文字列(String型)で変数 stringInTextField に読み込みます
    • EditText の text プロパティは Editable型 です。String型でないので toString() で変換しないといけません
  • stringInTextField 変数の内容(String型)を小数(Double型)に変換して cost変数に読み込みます
  • レイアウトでUI部品(View)につけられたid名(リソースID名, snake_case で付けられていることが多い)は 、View Binding では camelCase に変換した名前で利用します。たとえばid名が cost_of_service のUI部品は binding.costOfService.~ として参照します

UIのラジオボタンを読み取る

ラジオボタンの選択にあわせてチップの率を設定します

fun calculateTip() {
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
    val selectedId = binding.tipOptions.checkedRadioButtonId
    val tipPercentage = when (selectedId) {
        R.id.option_twenty_percent -> 0.20
        R.id.option_eighteen_percent -> 0.18
        else -> 0.15
    }
}
  • RadioGroup のインスタンスの checkedRadioButtonId を使って、選択されている RadioButton の id(リソースID)を変数 selectedId に取得します
  • when 文を使って、選択されているボタンのリソースIDに応じた率を変数 tipPercentage に取得します

チップの金額を計算をする

数学のライブラリを使って小数の計算をします

fun calculateTip() {
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
    val selectedId = binding.tipOptions.checkedRadioButtonId
    val tipPercentage = when (selectedId) {
        R.id.option_twenty_percent -> 0.20
        R.id.option_eighteen_percent -> 0.18
        else -> 0.15
    }
    var tip = tipPercentage * cost
    val roundUp = binding.roundUpSwitch.isChecked
    if (roundUp) {
        tip = kotlin.math.ceil(tip)
    }
}
  • 変数 tip にチップの額を計算して代入します
  • スイッチの On/Off(true/false)を isChecked を使って取得して、変数 roundUp に代入します
  • スイッチがOnのとき、変数tip を kotlin.math ライブラリの ceil() を使って切り上げ処理します
  • kotlin.math.ceil() のように書くことで、import せずに使うことができます
    • import するときは import kotlin.math.* とします

計算結果を書式を整えて表示する

計算結果を strings.xml に登録してある文字列に組み込んで表示してみますね

strings.xml を開いて、tip_amount のところを次のように書き換えます

<string name="tip_amount">Tip Amount: %s</string>

calculateTip関数 は次のように書き換えます

fun calculateTip() {
    val stringInTextField = binding.costOfService.text.toString()
    val cost = stringInTextField.toDouble()
    val selectedId = binding.tipOptions.checkedRadioButtonId
    val tipPercentage = when (selectedId) {
        R.id.option_twenty_percent -> 0.20
        R.id.option_eighteen_percent -> 0.18
        else -> 0.15
    }
    var tip = tipPercentage * cost
    val roundUp = binding.roundUpSwitch.isChecked
    if (roundUp) {
        tip = kotlin.math.ceil(tip)
    }
    val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
    binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
  • NumberFormat クラス(ドキュメント
    • getCurrencyInstance() メソッド・・・通貨としてフォーマット処理してNumberFormat型のインスタンスを返す
    • format() メソッド・・・Double型の数値を処理してString型で返す
  • NumberFormat は同じ名前のものが複数あるため Android Studio で赤く表示されます(自動で import を決定できない)。クリックして NumberFormat (java.text) を選んでおきます
  • getString()・・・strings.xml(文字列リソース)から文字列を取得する関数(ドキュメント
    • getString(リソースID)・・・リソースIDで指定された文字列を取得します
    • strings.xml に記述された文字列に %s などを使うことで書式設定することもできる。書式設定されている場合は getString(リソースID, 値, 値, ・・・) のように使います
    • 同じようなものに getText() というのもあります(少し仕様が異なる)
  • getString(R.string.tip_amount, formattedTip) は、strings.xml の tip_amount から文字列を取得、その文字列の %s の部分に変数 formattedTip の内容を代入しています
  • 最後に TextView の text プロパティに代入することで結果を表示しています

結果表示用のTextViewにプレースホルダーを設定する

プレースホルダー(placeholder)は、Android Studio でレイアウトをつくるときに目安としてとりあえず表示しておくテキストのことです

  • 結果表示用の TextView(リソースIDは tip_result)にプレースホルダーを設定してみます
  • TextView の text 項目の内容は、計算結果が出てからKotlinコードで設定しています。計算前に何かが表示されている必要はないので削除してしまいます

要らなくなった項目を削除します

  • activity_main.xml を開きます (app > res > layout > activity_main.xml)
  • TextView の id が tip_result のブロックを探します
  • android:text=”@string/tip_amount” の行を削除します

プレースホルダーを設定します

さきほど削除したあたりに次の行を追加します

tools:text="Tip Amount: $10"

プレースホルダーに設定した文字列が使われるのは Android Studio の Layout Editor の中だけ。アプリを実行するときには使わないので strings.xml に登録しなくて大丈夫。ハードコードしちゃっていいかも

テストとデバッグ

デバッグに Logcat を使ってみますね

  • アプリをエミュレータで動かすときに Run > Run ‘app’, でなく Run > Debug ‘app’ で実行します
  • 下の方にある「Logcat」ボタンをクリック(または View > Tool Windows > Logcat
  • アプリがクラッシュしたときの原因究明に Logcat に表示される内容が役立ちます
    • Logcat にはスタックトレースが表示されています(クラッシュするまでに呼ばれたメソッドがつぎつぎと表示されている)
    • たとえば空の入力をそのまま toDecimal にかけるとクラッシュしてしまいます(小数に変換できない)。Logcat を追えば toDecimal でエラーが起きていることを突き止めることができます
  • クラッシュの原因に応じて、たとえば toDouble のかわりに toDoubleOrNull() を使って小数に変換できない場合の処理を付け加えるなど、コードの修正を行います
    • null は「値が存在しない」ことを示します

警告をまとめてチェック

  • Android Studio 上で、グレーになっていたり下線があったりしたときはマウスカーソルをのせてみます。アドバイスが出るので必要に応じて修正します(修正も選択肢から選ぶだけで自動的に修正されることが多い)
  • すべての警告を一覧にして表示するには Analyze > Inspect Code… とします。チェックしながら問題点をつぶしていくことができます

ここで作成したコード

MainActivity.kt

package com.example.tiptime

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.tiptime.databinding.ActivityMainBinding
import java.text.NumberFormat

class MainActivity : AppCompatActivity() {

   private lateinit var binding: ActivityMainBinding

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)

       binding = ActivityMainBinding.inflate(layoutInflater)
       setContentView(binding.root)

       binding.calculateButton.setOnClickListener { calculateTip() }
   }

   private fun calculateTip() {
       val stringInTextField = binding.costOfService.text.toString()
       val cost = stringInTextField.toDoubleOrNull()
       if (cost == null) {
           binding.tipResult.text = ""
           return
       }

       val tipPercentage = when (binding.tipOptions.checkedRadioButtonId) {
           R.id.option_twenty_percent -> 0.20
           R.id.option_eighteen_percent -> 0.18
           else -> 0.15
       }

       var tip = tipPercentage * cost
       if (binding.roundUpSwitch.isChecked) {
           tip = kotlin.math.ceil(tip)
       }

       val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
       binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
   }
}

strings.xml

<string name="tip_amount">Tip Amount: %s</string>

activity_main.xml で修正するところ

...

<TextView
   android:id="@+id/tip_result"
   ...
   tools:text="Tip Amount: $10" />

...

build.gradle で修正するところ

android {
    ...

    buildFeatures {
        viewBinding = true
    }
    ...
}

まとめ

お疲れ様でした!

  • View Binding を使ったUI部品の利用
  • Double型と小数の計算(kotlin.math.*)
  • ラジオボタンの使い方。Radio Group , RadioButton , checkRadioButtonId
  • NumberFormat クラスを利用して数値を通貨の形式に変換する。NumberFormat , getCurrencyInstance() , format()
  • 書式を利用した文字列の処理
  • Logcat によるスタックトレースとデバッグ
  • コード中の警告をまとめてチェックする( Analyze > Inspect Code

参考