Android Kotlin Fundamentalsで学ぶ その3
はじめに
この記事はGoogleが提供しているCodelabの中のAndroidを作りながら学ぶAndroid Kotlin Fundamentalsコースで学習した内容を自分用に残していくものです。間違っていることなどあればコメントをいただけるとありがたいです!
この記事について
その3では、Lesson3について残していきます。
このレッスンでは、アプリ内のナビゲーションを作成する方法を説明しています。Fragment
を作成し、ナビゲーションを追加する流れで説明されています。ナビゲーションでは戻る操作(バックスタック)の宛先の変更や、外部アクティビティの呼び出し方法も学びます。
Create a fragment
3-1Android Trivia プロジェクトのダウンロード
ここからダウンロードしてください。
その中の AndroidTrivia-Starter を開いてください。
Fragment
Fragment とは、アクティビティの動作やUIの一部として動作する。1つのアクティビティで複数のフラグメントを組み合わせてマルチペインUIの作成や複数のアクティビティでフラグメントを再利用できる。
- 独自のライフサイクルがある
- 独自の入力イベントを受け取れる
- アクティビティの実行中にフラグメントの追加・削除ができる
- UIはxmlファイルで記述
Define navigation paths
3-2Navigationの追加
Navigationライブラリは画面遷移をいい感じに行なってくれる
Navigationの導入
- Projectレベルのgradleで,
ext {
...
navigationVersion = "2.3.0"
...
}
- アプリレベルのgradleで,
dependencies {
...
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
...
}
を追記する-> sync now
Navigationリソースファイルの追加
res
ディレクトリに対し、オプション->new
->Android Resource File
->ファイル名'navigationと指定すると
res/navigation/navigation.xml`が追加される。
FragmentにNavigationを追加
<fragment
...
app:navGraph="@navigation/navigation"
app:defaultNavHost="true"
...
/>
app:navGraph
属性に@navigation/navigation
を指定することでナビゲーショングラフリソースにあることを宣言
app:defaultNavHost
属性をtrue
にすることで、このナビゲーションホストがデフォルトのホストになり、[戻る]ボタンが傍受できる。
NavigationGraphにFragmentを追加
navigation.xml で、プレビューをみながらフラグメントのアクションなど追加できる。
見てわかるのがすごくありがたい
ハンドラを追加
アクションを起こすビューに対し、ハンドラを追加する。
今回はPLAY
ボタンに追加するので、
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// データバインディングの設定
val binding = DataBindingUtil.inflate<FragmentTitleBinding>(inflater, R.layout.fragment_title, container, false)
// playボタンのハンドラを追加
binding.playButton.setOnClickListener{view: View ->
view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment)
}
return binding.root
}
こんな感じ
バックスタックの管理をする
いわゆる[戻る]ボタンを押したらタイトルに戻るようにする。
ここではpopUpTo
, popUpToInclusive
属性を使って行う
-
popUpTo
: 対象のFragmentから上にあるバックスタックを全て破棄して、次のFragmentをスタックする -
popUpToInclusive
:-
true
:app:popUpTo
に指定したFragmentも含めてバックスタックを破棄して、新たなFragmentをスタックする -
false
: 指定したFragmentは残したままFragmentをスタックする。
-
App barの追加
NavigationUI
を使って実装する
- 戻るアクションの実装
override fun onCreate(savedInstanceState: Bundle?) {
...
// app bar の実装
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return navController.navigateUp()
}
- オプションボタンの作成
レイアウトは新たなレイアウトリソースファイル(リソースタイプ: Men)が必要
オプションボタンの表示はonCreateView()
内で
setHasOptionsMenu(true)
を定義する。
オプションボタンのonClickハンドラは
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.option_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item, requireView().findNavController())
|| super.onOptionsItemSelected(item)
}
とする。
Navigation Drawerを追加
よくあるハンバーガアイコンを押したら出てくるやつ
画面左端から右にスワイプしても出てくるよ
close | open |
---|---|
マテリアルデザインの追加
アプリレベルのgradleで、
dependencies {
...
implementation "com.google.android.material:material:$supportlibVersion"
...
}
を追加
drawer menuと drawer layoutを作成
こちらも新たなレイアウトリソースファイル(navdrawer_menu
)を作成
オプションボタンと同様、メニューレイアウトで作成し、アイテムを追加することで項目を増やしていく。
Navigation Drawerの表示
// Drawerレイアウトを表すメンバ変数を定義
private lateinit var drawerLayout: DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
...
NavigationUI.setupActionBarWithNavController(this, navController)
// navigation drawer の実装
drawerLayout = binding.drawerLayout
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return NavigationUI.navigateUp(navController, drawerLayout)
}
こんな感じ
Start an external Activity
3-3フラグメント間の引数の受け渡し
NavDirection
を使う。{key: value}の形で扱うことができる。
NavDirectionの追加
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
}
apply plugin: 'androidx.navigation.safeargs'
これらを追記することで、プロジェクト全体にNavDirection
が含まれるようになった。
*一度アプリをビルドしないと適用されないかも。。
NavDirectionクラスを追加, 引数の渡し側
GameFragment
からGameWonFragment
とGameOverFragment
へ問題数、回答数を送る。
まず、引数の設定をする。
navigation.xml
で引数の設定をする。Designでもできるけど、コードで書くとこうなる。
これはGameWonFragment
に引数を追加した時のやつ
<fragment
android:id="@+id/gameWonFragment"
<action
android:id="@+id/action_gameWonFragment_to_gameFragment"
app:destination="@id/gameFragment" />
<argument // 引数1
android:name="numQuestions"
app:argType="integer" />
<argument // 引数2
android:name="numCorrect"
app:argType="integer" />
</fragment>
次にGameFragment
で遷移させていたコードを変更する。
if (answers[answerIndex] == currentQuestion.answers[0]) {
...
} else {
// We've won! Navigate to the gameWonFragment.
view.findNavController().navigate(R.id.action_gameFragment_to_gameWonFragment)
}
} else {
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().navigate(R.id.action_gameFragment_to_gameOverFragment)
}
を
if (answers[answerIndex] == currentQuestion.answers[0]) {
...
} else {
// We've won! Navigate to the gameWonFragment.
view.findNavController().navigate(GameFragmentDirections.actionGameFragmentToGameWonFragment(numQuestions, questionIndex))
}
} else {
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().navigate(GameFragmentDirections.actionGameFragmentToGameOverFragment())
}
にする。
NavDirectionクラスを追加, 引数の受け側
GameWonFragment
で引数を受け取る
class GameWonFragment : Fragment() { // この行はそのまま
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val args = GameWonFragmentArgs.fromBundle(requireArguments()) // ここがBundleからの引数を受け取るためのコード
...
}
return binding.root
}
}
共有機能を追加する
インテントを使って他のアプリのアクティビティに移動することができる。
- 共有するためにインテントを作成
private fun getShareIntent(): Intent{
val args = GameWonFragmentArgs.fromBundle(requireArguments())
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.setType("text/plain")
.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_success_text, args.numCorrect, args.numQuestions))
return shareIntent
}
Intent
はACTION_SEND
インテントを使うことで、メッセージの送信を行うことができる。
また、setType
メソッドでタイプの指定をし、putExtra
で送信するメッセージを指定する。
- 共有の開始を呼び出す
private fun shareSuccess(){
startActivity(getShareIntent())
}
- 共有ボタンの表示
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.winner_menu, menu)
if(getShareIntent().resolveActivity(requireActivity().packageManager) == null){
menu.findItem(R.id.share).isVisible = false
}
}
- メニューから共有を呼び出す
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.share -> shareSuccess()
}
return super.onOptionsItemSelected(item)
}
こんな感じに共有先が出てくれる
共有先のアクティビティは自動で見つけてくれるみたい。すごい。。
他にも、Sharesheet
を使用することで、好きな人と情報を共有することもできるみたい。
Android側から提供されているこういった機能はとても気が利いている
まとめ
今回は、FragmentやNavigationの使い方について学びました。Fragmentをうまく使うことでUIの充実度がかなり増すことがわかりました。また、NavigationでFragmentを正しく遷移することができれば快適なアプリケーションを作成することができるとわかりました。このレッスンで抑えるべきこととして、Navigationによる遷移の宛先や、バックスタックで管理する画面など、遷移元、遷移先の関係を十分理解することだと思いました。遷移の辻褄が合わないと快適なUIを作ることができないと実感しました。この章は自分なりにもっと噛み砕いて理解していきたいと思います。
Discussion