22新卒技術研修で実施したAndroidアプリ開発研修の講義資料です。 動画:https://youtu.be/tqCDiQygR70
ハンズオン用リポジトリ https://github.com/mixigroup/AndroidTraining2022
Android™研修 2022ver2022/04/26Android は Google LLC の商標です。
View Slide
講師・チューター紹介● 講師○ 山田 淳登 (slack: atsuto.yamada)○ 2015新卒○ TIPSTAR Android版 開発リーダー● チューター○ 太田 祐樹 (slack: yuki.ota)○ 2021.09入社 2014高専卒○ Androidは2018年頃からやっています
研修の目的と範囲● 目的○ 基礎的な部分の学習○ Android開発の特徴・魅力が分かる● 範囲○ Androidの実行環境の概要○ UIレイアウト○ リスト(RecyclerView)○ 非同期処理とKotlin Coroutine○ リアクティブストリーム( LiveData)○ MotionLayout○ ハンズオン(ストップウォッチを作ってみよう)■ 一緒にやるハンズオンと、自分で手を動かしてもらうハンズオンがある※Kotlin文法は範囲外10:31
はじめにこの研修の最終目標ラップ機能付きストップウォッチを作る10:32
Androidアプリの基本構造①10:32
Androidネイティブアプリとは● Androidランタイム(ART)上で動くアプリ○ Java Virtual Machineではない○ Java APIが利用できる○ ただし独自実装のため、 Javaとは動作が異なるAPIも存在する● 開発言語は主にJava or Kotlin○ 今ではKotlinが主流○ バイトコード(class)を吐き出すことができれば良い■ 例えばScalaでも開発可能10:32
JavaとKotlinの関係● Java, Kotlin どちらもバイトコードを生成するための手段● Kotlinでしか利用できない独自APIがある○ これが開発者がKotlinを選ぶ最大の理由JavaKotlinバイトコード.class実行可能形式.dexパッケージ.apk︙10:33
開発環境● Android Studioと呼ばれるIDEを利用○ IntelliJ IDEAベース10:33
ディレクトリ構成● 初期状態では実際のディレクトリ構成で表示されない● 左側のディレクトリツリーでProjectに切り替えると実際のディレクトリ構成になる● こちらの方が分かりやすいのでオススメ10:34
ディレクトリ構成● app/src/main/res○ リソースを入れる場所■ 画像、図形、UIレイアウト、テキスト など● app/src/main/java○ Java, Kotlinのコードを入れる場所● app/build.gradle○ アプリのビルド時に必要な情報を記すファイル■ SDKバージョン■ 外部ライブラリ● app/src/main/AndroidManifest.xml○ アプリの構成情報を記すファイル10:34
リソースディレクトリ● app/src/main/res○ values/strings.xml■ アプリ内のUIで表示する文字列を定義する■ Java, Kotlinコード上に表示文字列を書くことはほぼ無い● 実行環境の言語設定によって内容を変えるため○ values/colors.xml■ アプリで利用する色コードを定義する○ values/themes.xml■ UIパーツの見た目の変更を定義する10:35
app/build.gradle● ビルド時に必要な情報が定義されている● アプリのバージョン● compileSdk○ どのAndroid SDKバージョンでビルドするか○ 32 = Android 12● minSdk○ 動作可能な最低のAndroidバージョン● パッケージ名(applicationId)○ アプリを特定するための ID● 外部依存ライブラリ(dependencies)10:35
AndroidManifest.xml● アプリの構成情報● アプリにどんな画面があるのか(Activity)● アプリにどんなサービスがあるのか(Service)○ サービスとは?■ 画面を持たないバックグラウンド処理を行うもの■ 消費電力節約の観点から、近年多用はされない■ プッシュ通知で使われる● アプリ名● アプリアイコン● 外部アプリから自分のアプリ画面への呼び出し方法の定義(IntentFilter)○ 例:別アプリで「共有」を押すと、データを受け取りつつ自分のアプリを開く10:36
Androidアプリの基本構造②10:36
Activity● Androidでは1枚の画面をActivityという機能を利用して実装する○ AppCompatActivity:Androidバージョン間の互換性を維持するためのクラス● 画面内で行う処理は、Activityを起点に処理が伸びていく● 画面ごとにAppCompatActivityを継承したクラスを作成する10:37
Activityのライフサイクル● ライフサイクルとは○ 画面のステートによって呼ばれるメソッドと、その順序● 画面のステート○ ステートの例:画面を開く、画面を起動したまま別アプリを開く、画面を閉じる● onCreateで画面の初期化を行う● onDestroyで画面の終了処理を行う画面の起動onCreateonStartonResume画面の表示onPauseonStoponDestroy画面の終了画面が見えなくなる10:37
ハンズオン①:サンプルプロジェクトの起動● サンプルプロジェクトを起動してみる● プロジェクトをCloneする。以下をターミナルで実行cd ~mkdir AndroidStudioProjectscd AndroidStudioProjectsgit clone[email protected]:mixigroup/AndroidTraining2022.gitcd AndroidTraining2022git checkout lesson110:50
ハンズオン①:サンプルプロジェクトの起動● AndroidStudioを起動● Openをクリック10:50
ハンズオン①:サンプルプロジェクトの起動● 先程Cloneしたディレクトリを選択し、Openをクリック● 「Trust Gradle Project?」と出たら、Trust Projectをクリック10:50
ハンズオン①:サンプルプロジェクトの起動● アプリをエミュレーター上で実行する10:50
11:15まで休憩時間11:15
UIレイアウト11:15
View● Viewとは:画面上に何かを表示するためのコンポーネント● Viewを継承したさまざまなクラスがSDKで用意されている● 代表的なView○ TextView:文字列を表示○ ImageView:画像を表示○ Button:ボタンを表示○ EditText:文字列入力フォームを表示● 画面で必要なViewをXMLで記述する(レイアウトXML)11:16
レイアウトXMLとActivity(Kotlinコード)の関係● Viewを利用するにはインスタンスが必要● レイアウトXMLをViewインスタンスに変換することでプログラム上で利用可能となる● レイアウトXMLはViewインスタンスを用意するための手段○ レイアウトに限らず、 AndroidでのXML定義要素はすべてインスタンス化して利用する● レイアウトXMLをインスタンス化する機能はLayoutInflaterが提供● 1つのXMLに存在するViewをまとめるためのHolderとしてViewBindingが利用される● ActivityにsetContentViewすることで、その画面のUIとして利用されるXMLViewViewViewViewViewViewBinding11:17
TextView● 文字列を表示するためのView● XML属性を変更することで書式の変更が可能○ android:id Viewを特定するためのID○ style 別途定義した属性を引っ張ってくる○ android:text 表示するテキスト内容○ android:textColor テキスト色○ android:textSize テキストサイズ● リソースとして定義した値は @.../ で参照○ @color/○ @string/11:18
Button● ボタンを表示するためのView● TextViewのサブクラスなので、TextViewの属性と共通11:18
サイズ単位 sp, dp● 世の中のAndroid端末のディスプレイは、高解像度・低解像度が混在している● よって、pixel単位をそのまま利用できない○ 低解像度ディスプレイでは大きく、高解像度ディスプレイでは小さく表示される● サイズ指定は dp を使う○ 画面のピクセル密度を考慮したサイズ指定が可能● テキストサイズ指定では sp を使う○ 画面のピクセル密度に加え、 OSのフォントサイズ設定によって変化する単位○ フォントサイズ標準時、 14sp = 14dp11:19
ViewGroupとは● ViewGroupとは:Viewを複数並べるためのコンポーネント● 代表的なViewGroup○ FrameLayout:重ねて表示○ LinearLayout:縦方向や横方向に並べて表示○ ConstraintLayout:View同士の制約(並べ方)を定義して表示○ ScrollView:表示領域をはみ出るくらいの大きな Viewをスクロール可能にする○ RecyclerView:スクロール可能な表示領域のみ Viewを配置する● これも同じくレイアウトXMLに記述する11:20
LinearLayout● 縦方向や横方向に並べて表示する● android:orientation○ vertical 縦に並べる○ horizontal 横に並べる11:21
ConstraintLayout● View同士の制約を設定して並べるstart(左側面)を親の左側面にくっつけるtop(上側面)を親の上側面にくっつけるstart(左側面)をTEXT_1の右側面にくっつけるtop(上側面)をTEXT_1の下側面にくっつける11:21
ViewGroupに対しての属性指定● ViewGroupに属しているViewは、親に対してどのようなサイズ・位置にするかを指定できる○ layout_…がついている属性は親に対する指定● android:layout_width, android:layout_height○ wrap_content Viewのコンテンツが収まるように○ match_parent 親要素の大きさに合わせる● android:layout_margin…○ マージンを取って位置を決定する11:22
ConstraintLayout Chain● ConstraintLayoutにおいて、お互いに依存し合った制約が設定されている場合、うまくセンタリングされる● その際の並べ方を指定できる● android:layout_constraint(Horizontal/Vertical)_chainStyle○ packed○ spread○ spread_insidepacked spread spread_inside11:23
ConstraintLayout Chainの作り方(エディタ上)● (実際に解説 ブランチ名:example1)● Designを開きComponent Treeから複数のViewを選び、右クリックで作成11:30
ハンズオン②:【課題】秒数表示・ボタンを置く● ブランチ lesson2 をcheckout● 課題○ MainActivityに紐付いているactivity_main.xmlを編集○ 右のようにTextViewとButtonを配置する○ (必要なパラメーターは次ページ)● 時間:10分間(11:47まで)11:47
ハンズオン②:【課題】秒数表示・ボタンを置くマージン36dpマージン36dp均等均等11:45
ハンズオン②:正解解説● (実際に解説 ブランチ名:lesson2-answer)11:45
ActivityからViewを操作する12:00
ActivityにViewを設定する● XMLで定義したViewは binding に格納されている● activity_main.xml → ActivityMainBinding● setContentViewでActivityの画面として設定する12:01
ActivityからViewを操作する● id: time_text → timeText● binding内にインスタンスがあるので、そのプロパティをコード側で変更できる12:02
任意のタイミングでViewを操作する● setOnClickListenerを使うことで、Viewがクリックされた時に動作させることができる12:03
リストの表示12:03
なぜRecyclerViewを使うのか● リスト表示ではRecyclerViewの利用が必須● LinearLayoutを使ってリストを作成するとアイテムが多数あった場合、描画する必要の無い領域までViewがレイアウトされる○ リストの要素が100個あればTextViewが100個必要になる○ 見えている領域は十数個だけ。十数個しか使わないのに無駄︙画面領域が赤線部分だとするとこの領域外のTextViewが無駄12:04
なぜRecyclerViewを使うのか● RecyclerViewを使うと、表示領域分だけViewをレイアウトできる● スクロールすると必要に応じてViewが新規作成・再利用される12:05
なぜRecyclerViewを使うのか● スクロールすると、領域外に出たViewがリサイクルされる● リサイクルされたViewは次に見えるアイテムとしてレイアウトされる12:05
RecyclerViewを使う● リスト内の1アイテム分のレイアウトXMLを新規作成する● 表示箇所にRecyclerViewを配置● app:layoutManager○ 並べ方を設定○ androidx.recyclerview.widget.LinearLayoutManager■ LinearLayoutのように縦方向に並べる12:06
RecyclerViewを使う● RecyclerView.Adapterを実装する○ リストにあるデータをViewに変換する● ハンズオンではこの実装である ListAdapter を利用● onCreateViewHolder○ ViewHolderを返す■ アイテム内で使うViewを保持するクラス■ 歴史的にはViewBindingが後発● onBindViewHolder○ positionに位置するデータを Viewに設定する12:06
ハンズオン③:リスト表示する● 一緒にやるハンズオン● ブランチ lesson3 をcheckout● 解説○ ボタンを押すとリストにアイテムが追加される実装○ Adapterの実装○ MainActivity側の実装12:15
昼休憩:13:30まで13:30
非同期処理13:30
Androidにおける非同期処理● Androidにおける処理スレッドの種類○ Mainスレッド(UIスレッド)○ IOスレッド(バックグラウンドスレッド)● onCreateなどのフレームワークからのコールバックはMainスレッドで呼ばれる● Mainスレッドで重い処理やネットワーク通信を行うと○ 処理(通信)が終わるまでアプリが固まる○ ネットワーク通信はフレームワークの制約によりそもそもできない(クラッシュする)● Viewの操作はMainスレッドで行う必要がある○ IOスレッド→Mainスレッドへのコールバックが必要13:30
Kotlin Corouines● 同一ブロック内で中断可能なスコープ○ →CoroutineScope○ Activityに連動したCorouineScopeはlifecycleScope.launch で生成● 通常 Thread.sleep を使うとスレッドが停止する● delay を使うとスレッドが停止せず、別ブロックにある処理を続行する● 実行スレッドを同一ブロック内で変更可能
Kotlin Corouines● 処理が不要になったら適切にCoroutineScopeが終了される○ アプリが終了すれば停止する● while無限ループもガシガシ使える13:33
Kotlin Corouines● withContextを利用することで、そのブロックだけ別のスレッドで実行が可能● (無駄ではあるが)インクリメントだけIOスレッドで実行している○ Dispatcher.MainでMainスレッドでの実行も指定できる。13:35
ViewModelとLiveData13:36
責務の分離● 画面に機能を実装していくとActivityの肥大化する○ 可読性の低下○ メンテナンスコストの増加○ バグの増加● 各クラスが特定の処理に集中できる設計が重要○ →ViewModelの登場13:37Activity ViewModelViewModelが用意したデータをViewに設定することに集中UI表示するデータを用意することに集中データ
ViewModel● AndroidViewModel を継承したクラスを用意● by viewModels()○ Activity内でのViewModel生成13:38
LiveData● ViewModelがActivityに依存することは許されない○ インスタンスの生存期間が異なる・責務分離の明確化 などの理由● ViewModelからActivityにデータを伝える方法が必要○ →LiveDataを使う● LiveData:データの入れ物● 更新を通知する機能が備わっている● LiveData.observe○ データの変化時に実行されるリスナーを設定する13:39
LiveDataの仲間たち● MutableLiveData○ 外部から自由に変更可能な LiveData○ setValue(Kotlin上からは value と省略)でデータを設定● MediatorLiveData○ 別のLiveData(ソース)を元にデータ設定できる LiveData○ addSource■ 別のLiveDataソースを設定■ ソースの値変更されたら、ブロックが呼ばれる13:40
MediatorLiveDataのショートカットたち● LiveData.map { }○ 他のLiveDataの値を変換する13:41
MediatorLiveDataのショートカットたち● LiveData.switchMap { }○ 他のLiveDataの値が変化したら、参照する LiveDataを切り替える○ ※liveData { } ブロックは次に紹介13:42
Coroutine LiveData(liveData{ })● ActivityがObserveしている間だけ動作するCoroutineScopeを生成● ブロック内でデータを生成し、emit()することで値を更新する13:45
ハンズオン④:LiveDataでステートを管理する● 一緒にやるハンズオン● ブランチ lesson4 をcheckout● ViewModelにあるLiveDataでストップウォッチのステートを管理する● ステートを元に右側のボタンの以下を変更○ テキスト○ 色○ クリックアクション14:05
ハンズオン⑤:ストップウォッチの秒数を表示する● ブランチ lesson5 をcheckout● 課題○ MainViewModel.currentTimeを元に、currentTimeTextの中身を実装○ currentTimeTextをTextViewに表示● 時間:20分間(14:33まで)14:33
ハンズオン⑤:ストップウォッチの秒数を表示する● (解説 ブランチ名:lesson5-answer)14:35
14:50まで(10分)休憩
MotionLayout14:50
MotionLayout● Viewを移動させるコンポーネント(ViewGroup)● ConstraintLayoutのサブクラス● 開始状態と終了状態の制約を定義、その間をアニメーションさせる○ レイアウトXMLとは別に、MotionSceneというXMLを用意する14:50
MotionLayoutTextView(time_text)を移動させる例親要素ConstraintLayoutをMotionLayoutに変更app:layoutDescriptionを追加MotionSceneが定義されたxmlを指定動かしたいViewの制約を削除14:51
MotionLayoutTransitionに開始・終了ステートと、遷移時間を設定Constraintで開始ステートにおける制約を設定Constraintに終了ステートにおける制約を設定14:52
MotionLayout● transitionToEnd()○ 終了ステートに遷移● transitionToStart()○ 開始ステートに遷移14:53
ハンズオン⑥:MotionLayoutを使ってViewを移動させる● 一緒にやるハンズオン● ブランチ lesson6 をcheckout● ラップボタンを押すと、time_text を上に上げつつ、recycler_view_header, recycler_viewが見えるようにする15:10
ハンズオン⑦:最終課題● ブランチ lesson7 をcheckout● 1時間(16:15まで)● ラップ機能を完成させる○ ストップウォッチ開始中だけ、ラップボタンを有効にする(Button.isEnabled)○ ラップ時間表示を実装する○ ラップがあるときは時間表示を上げる○ ラップがないときは時間表示を下げる● 完成したら、別ブランチで差分をpushし、lesson7へのPRを作成16:10