カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit3 – 1 第3回 後編:アプリバーとメニュー

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit3 – ナビゲーションUnit3-1 第3回 後半 の内容をまとめたものです(翻訳ではありません)


Unit3 – 1 第3回 後編

アプリバーにメニューを設定する方法を説明しています

アプリバーのメニューとアイコンを設定

アクションバーっていうことも

  • アプリバーにオプションメニューを加えてみます
  • アプリ画面の一番上にバーが付いていることがあり、アプリバー(app bar)とかアクションバーと呼ばれます
  • アプリの名前が表示されていたりしますが、カスタマイズすることで他にもいろんな機能を実現できます
  • よくつかうアクションのショートカットや、オーバーフローメニュー(アプリバーに表示しきれなかったアクションボタンを自動的にまとめる)を表示したりできます
  • 今回は、A~Z の文字を表示するのに、メニューオプションを使ってリストとグリッドのレイアウトを切り替えられるようにします

プロジェクトにアイコンをインポート

まずメニューに使うアイコン画像をインポートですね

  • プロジェクトにグリッドとリストをあらわすアイコンを2つインポートします
  • Resource Manager から Vector Asset を開きます
  • Clip Art を選択したら適当なアイコンを選び、まず “view module” として ic_grid_layout という名前でアイコンをインポートします
  • 続いて “view list” として ic_linear_layout という名前で適当に選んだアイコンを同様にインポートします
  • マテリアルアイコンの追加方法について、詳しくは Unit2 – 1 第8回 前半 をどうぞ

アプリバーに表示するメニューを作成

メニューのレイアウトを作成しますね

  • 新しいリソースファイル(layout_menu.xml)を作成します
  • resフォルダを右クリックして、New > Android Resource File を選択、ダイアログを次のように設定します
    • File Name : layout_menu
    • Resource Type : Menu
  • OK ボタンをクリックするとファイルが作成されます
  • layout_menu.xml を開きます(res > menu > layout_menu.xml)
  • 次のように書き換えます
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item android:id="@+id/action_switch_layout"
       android:title="@string/action_switch_layout"
       android:icon="@drawable/ic_linear_layout"
       app:showAsAction="always" />
</menu>
  • menuファイルの構造はシンプルです
  • menuタグで始まって、その中にそれぞれのオプションが記述されます
  • このメニューにセットするボタンはひとつだけで、次のようなプロパティが記述されています
    • id・・・View のときと同じくKotlinコードから参照するのに使います
    • title・・・このアプリではスクリーンリーダでメニュー項目を読み上げるのに使われます
    • icon・・・デフォルトとして ic_linear_layout を設定しています。ボタンが選択されると状態が切り替わりグリッドアイコンが表示されます
    • showAsAction・・・システムにボタンをどんな風に表示するのかを示します。always が設定されているので、このボタンはいつでもアプリバーで見えるようになります。オーバーフローメニューには入りません
  • 作成したメニューを実際に動作させるには MainActivity.kt にコードを追加する必要があります

メニューを動作させるコードを記述

メニューを選択するたびに画面レイアウトが変わるようにします

メニューのアイコンも変わるんですよね

アプリバーにメニューボタンを表示するのに、MainActivity.kt に少し手を加えます

状態を追跡するためのプロパティの設定

  • まず、今のアプリのレイアウトがリニアとグリッド、どちらなのか状態を追跡するためのプロパティを作っておきます
  • プロパティの名前は isLinearLayoutManager とし、true でリニアレイアウトマネージャが選択されているものとします
  • MainActivity.kt のプロパティとして、ファイルの上のほうに次のように記述します
private var isLinearLayoutManager = true

レイアウトマネージャを切り替える関数を作成

画面レイアウトを変えるコードです

  • ユーザがボタンをタップしたら、UI画面の表示をグリッドに切り替えます
  • レイアウトマネージャにはいろいろなものがあります。今回は LinearLayoutManger と GridLayoutManager を切り替えて使います
  • MainActivity.kt につぎの関数を加えます
private fun chooseLayout() {
    if (isLinearLayoutManager) {
        recyclerView.layoutManager = LinearLayoutManager(this)
    } else {
        recyclerView.layoutManager = GridLayoutManager(this, 4)
    }
    recyclerView.adapter = LetterAdapter()
}
  • レイアウトマネージャの設定は、Kotlin では recyclerView インスタンスの layoutManager プロパティを使います
  • アダプタ(LetterAdapter)はリストとグリッド、どちらのレイアウトになっていても影響ありません

アイコンをセットする関数を作る

こっちはアイコンを変えるコードですね

  • XMLファイル(layout_menu.xml)で、アイコンはリニア表示のものに初期設定されています
  • メニューをタップしてレイアウトを切り替えたら、それに合わせてアイコンを更新する必要があります
  • リニアとグリッドのアイコンを、表示されるレイアウトに合わせてセットして、タップされたら切り替わるようにします
  • MainActivity.kt につぎの関数を加えます
private fun setIcon(menuItem: MenuItem?) {
   if (menuItem == null)
       return

   // Set the drawable for the menu icon based on which LayoutManager is currently in use

   // An if-clause can be used on the right side of an assignment if all paths return a value.
   // The following code is equivalent to
   // if (isLinearLayoutManager)
   //     menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
   // else menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
   menuItem.icon =
       if (isLinearLayoutManager)
           ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
       else ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
}

メニューを生成する

  • アプリバーでメニューを使うときは、最低でも2つのメソッドをオーバーライドする必要があります
    • onCreateOptionsMenu・・・オプションメニューを生成します
    • onOptionsItemSelected・・・メニューが選択されたときの処理を記述します
  • onCreateOptionsMenu() をオーバーライドすると次のようになります

メニューを生成するコードですね

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
   menuInflater.inflate(R.menu.layout_menu, menu)

   val layoutButton = menu?.findItem(R.id.action_switch_layout)
   // Calls code to set the icon based on the LinearLayoutManager of the RecyclerView
   setIcon(layoutButton)

   return true
}
  • レイアウトを layout_menu.xml から生成(inflate)します
  • アイコンが現在のレイアウトに基づいて正しく設定されるように setIcon() を呼びます
  • メソッドは Boolean を返すようにします
  • オプションメニューが生成されるようにしたいので true を返します

メニューが選択されたときの処理を記述

  • onOptionsItemSelected() メソッドをオーバーライドします
  • オプションメニューが選択されたら isLinearLayoutManager をレイアウトに合わせて設定します
  • つぎに chooseLayout() を呼びだして RecyclerView のレイアウトマネージャとアダプタを設定します
  • さいごに setIcon() を呼び出して、アプリバーのメニューアイコンをレイアウトに応じて設定します
  • onOptionsItemSelected() を以下のように記述します

こっちはメニューが選択されたとき

override fun onOptionsItemSelected(item: MenuItem): Boolean {
   return when (item.itemId) {
       R.id.action_switch_layout -> {
           // Sets isLinearLayoutManager (a Boolean) to the opposite value
           isLinearLayoutManager = !isLinearLayoutManager
           // Sets layout and icon
           chooseLayout()
           setIcon(item)

           return true
       }
       //  Otherwise, do nothing and use the core event handling

       // when clauses require that all possible paths be accounted for explicitly,
       //  for instance both the true and false cases if the value is a Boolean,
       //  or an else to catch all unhandled cases.
       else -> super.onOptionsItemSelected(item)
   }
}

アプリバーのメニューがタップされると呼び出されます。どのメニュー項目がタップされたのか判定するために when を使ってメニュー項目の id をチェックしています

RecyclerView のリストデータを更新したときは、recyclerView.adapter?.notifyDataSetChanged() で表示を更新できるそうです

MainActivity クラスの onCreate() を修正

MainActivity を開いたときのレイアウトマネージャとアダプタの設定も、作成した関数を使うようにしておくといいかも。コードがすっきり

  • レイアウトマネージャとアダプタを chooseLayout() に記述したので、onCreate() にあるレイアウトマネージャとアダプタの呼び出しも chooseLayout() を使うように修正します
  • 変更を加えると onCreate() はこんな感じになります
override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

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

   recyclerView = binding.recyclerView
   // Sets the LinearLayoutManager of the recyclerview
   chooseLayout()
}

MainActivity クラスのまとめ

MainActivity クラスはこんな感じになります

MainActivity クラスをまとめるとこんな感じになります

class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private var isLinearLayoutManager = true

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

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

recyclerView = binding.recyclerView
// Sets the LinearLayoutManager of the recyclerview
/*
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = LetterAdapter()
*/
chooseLayout()
}

private fun chooseLayout() {
if (isLinearLayoutManager) {
recyclerView.layoutManager = LinearLayoutManager(this)
} else {
recyclerView.layoutManager = GridLayoutManager(this, 4)
}
recyclerView.adapter = LetterAdapter()
}

private fun setIcon(menuItem: MenuItem?) {
if (menuItem == null)
return

// Set the drawable for the menu icon based on which LayoutManager is currently in use

// An if-clause can be used on the right side of an assignment if all paths return a value.
// The following code is equivalent to
// if (isLinearLayoutManager)
// menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
// else menu.icon = ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
menuItem.icon =
if (isLinearLayoutManager)
ContextCompat.getDrawable(this, R.drawable.ic_grid_layout)
else ContextCompat.getDrawable(this, R.drawable.ic_linear_layout)
}

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.layout_menu, menu)

val layoutButton = menu?.findItem(R.id.action_switch_layout)
// Calls code to set the icon based on the LinearLayoutManager of the RecyclerView
setIcon(layoutButton)

return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_switch_layout -> {
// Sets isLinearLayoutManager (a Boolean) to the opposite value
isLinearLayoutManager = !isLinearLayoutManager
// Sets layout and icon
chooseLayout()
setIcon(item)

return true
}
// Otherwise, do nothing and use the core event handling

// when clauses require that all possible paths be accounted for explicitly,
// for instance both the true and false cases if the value is a Boolean,
// or an else to catch all unhandled cases.
else -> super.onOptionsItemSelected(item)
}
}
}

アプリを実行します

  • アプリをビルドして実行してみます
  • アプリバーのメニューボタンをタップして、リストとグリッドが切り替わることを確認します

このチュートリアルのプロジェクトは こちら からダウンロードできます

ちゃんと動けば完成です!

まとめ

おつかれさまでした

  • 明示的インテント(explicit intent)はアプリの中の特定のアクティビティへ移動するのに使います
  • 暗黙的インテント(implicit intent)は特定のアクション(たとえばリンクを開くとか、画像をシェアするとか)に対応するものです。インテントをどんな形で実行するかはシステムが決定します
  • メニューオプションはアプリバーにボタンやメニューを追加するのに使います
  • コンパニオンオブジェクト(companion object)は、使い回せる定数を設定するものとして利用できます
  • インテントは次のような手順で実行します
    • context への参照を取得します
    • アクティビティ(明示的)、またはインテントタイプ(暗黙的)から Intent オブジェクトを作成します
    • putExtra() を使って必要なデータをインテントに渡します
    • Intent オブジェクトを startActivity() に渡して呼び出します

参考