Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlinで作るAndroidアプリ開発入門

 Kotlinで作るAndroidアプリ開発入門

【学生限定】Kotlinで作るAndroidアプリ開発入門 @ヤフー OthloEvent#38
https://othlotech.connpass.com/event/96786/

サンプルコード
https://github.com/mura/othloevent-android

0875646c456281936f571c676da7aee0?s=128

Yohei Murayama

November 11, 2018
Tweet

More Decks by Yohei Murayama

Other Decks in Programming

Transcript

  1. Kotlinで作るAndroidアプリ開発⼊⾨ OthloEvent#38 / Yohei Murayama

  2. 今⽇の流れ • Androidについて • ボタンの実装 • テキストボックスの実装 • データの保存の仕⽅ •

    画⾯遷移 • リスト表現
  3. Androidとは

  4. Androidのしくみ • Googleが開発しているOSS • Linuxカーネル上で動作 • Java VMっぽいものが動いている(ART) ◦ すべてのアプリはこのVM上で動作

    • Java APIが提供されている
  5. None
  6. Androidアプリの作り⽅ • Win/Mac/LinuxのPC • Android Studioで開発 • Java/Kotlinで記述 • エミュレータ

    or デバッグモードにした実機で動作確認
  7. その他のAndroidアプリの作り⽅ • Flutter (Dart) / Google • Xamarin.Android (C#) /

    Microsoft • React Native (JavaScript) / Facebook • Unity (C#) / Unity Technology
  8. プロジェクトを作ろう

  9. None
  10. None
  11. None
  12. None
  13. None
  14. 動かしてみよう

  15. None
  16. None
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. ボタンを実装してみよう

  24. こういうものを作ってください

  25. 課題1 • 画⾯にボタンを追加してみよう • ボタンをクリックしたら、⽂字が変わるようにしてみよう

  26. ConstraintLayout • View同⼠を結びつけて配置する ◦ 制約=Constraint • いろいろなバージョンのAndroidで互換性がある • Android Studioでデザインできる

  27. None
  28. None
  29. コードを書く • ボタンやテキストはそれぞれ1つのインスタンス(まとめてViewと呼ぶ) • ActivityでViewを取得して操作する • レイアウトからView取り出すメソッドが findViewById()

  30. これだけは覚えてほしいショートカット • Shift 2回 ◦ 何でも検索する。ファイルもメニューもなんでもあり • Alt(option) + Enter

    ◦ 困ったときの味⽅。コード上に波線や警告がでたらとりあえず押す
  31. MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView = findViewById<TextView>(R.id.textView) } } setContentView()でxmlから Viewのインスタンスを作成 findViewById()でレイアウトからほしいViewを探す
  32. valとvar // あとから変更できないインスタンス(=final) val textView = findViewById<TextView>(R.id.textView) textView = findViewById<TextView>(R.id.otherTextView)

    // NG // あとから変更できるインスタンス var name = "Yohei Murayama" name = "Yahoo! JAPAN" // OK
  33. Kotlinの型 • 型推論ありの静的型付き⾔語 • 静的型付:コンパイル時に型が決まる • 型推論:コンパイラが⾃動で型を決める • Nullを⼊れたい場合は特別な書き⽅が必要

  34. Kotlinの型 // 変数名の後ろに型が書ける val count: Int = 0 // キャストしたいときは

    as を使う // キャストしたのでtextVewは⾃動でTextView型になる val textView = view as TextView
  35. コールバックの引数(クリックされたView) が返される Buttonのクリック val button = findViewById<Button>(R.id.button) button.setOnClickListener { view

    -> // クリックしたときに実⾏されるブロック } findViewById()で探し出すViewの型を指定 ブロック⾃体はコールバックのラムダ式
  36. 同じ処理をJavaで書いた場合 button.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { //

    クリックしたときに実⾏されるブロック } });
  37. クリックしたときの処理 val textView = findViewById<TextView>(R.id.textView) val button = findViewById<Button>(R.id.button) button.setOnClickListener

    { _ -> textView.text = "Yohei Murayama" } 使わない引数は _ で⽰す プロパティアクセスのように⾒えて実はsetText()を叩いてい
  38. ⼤事なこと • 困ったことがあれば、Android Developersのサイトを⾒るようにしましょ う ◦ https://developer.android.com/ • Qittaもよいですが、情報は鵜呑みにせずに最後にはDevelopersサイトで 確認するようにしましょう

  39. はやくできた⼈は • ⽂字の⾊を変えてみよう • ⽂字の⼤きさを変えてみよう • ⽂字の位置を変えてみよう

  40. ⽂字の取得してみよう

  41. 課題2 • テキストボックスで⾃由に⽂字を書けるようにしよう • ボタンを押すと⼊⼒した⽂字にTextViewを書き換えよう

  42. None
  43. MainActivity.kt val editText = findViewById<EditText>(R.id.editText) button.setOnClickListener { _ -> textView.text

    = editText.text } プロパティアクセスのように⾒えて実はgetText()を叩いている テキストボックスはEditTextクラス
  44. Kotlinでのgetter/setter • Javaではフィールドにアクセスするためget~()/set~()というメソッドを作る ことが多い • Kotlinではフィールドの定義をすると、⾃動的にgetter/setterが作られる ◦ Javaから呼び出すときはgetter/setter経由で呼び出す • Kotlinの内部的にはプロパティアクセスはすべてgetter/setter経由で⾏われる

    • Javaと混在させない限りあまり気にしなくて良い
  45. 早くできた⼈は • レイアウトが縦に伸びて⼊⼒しにくいので変更してみよう • EditTextに⽂字を⼊⼒したらすぐにTextViewに反映するようにしてみよう ◦ ヒント:addTextChangedListener と TextWatcher

  46. データを保存してみよう

  47. Androidでのデータ永続化 • SharedPreferences • SQLite • 内部ストレージ(ファイル) • 外部ストレージ(ファイル) •

    ネットワーク https://developer.android.com/guide/topics/data/data-storage?hl=ja
  48. SharedPreferences • キーと値を保存するストレージ(KVS) • おもにプリミティブ型のデータを格納する • 設定値など⼩さいデータの保存に向いている • 画像や⾳楽など⼤きなデータには適さない

  49. 課題3 • ボタンを押したとき、⼊⼒した⽂字を保存しよう • アプリを終了し、再度⽴ち上げたときに保存した⽂字を表⽰しよう

  50. Activityのライフサイクル Activityが⽴ち上がるとまず ここが呼ばれる ホームボタンを押して Activityを閉じるとここ 戻るボタンを押してActivity を閉じるとここ(⼤体は onDestroy()まで進む) onResume()までこないと 画⾯は表⽰されていない

  51. ライフサイクルを確認しよう • onCreate(), onStart(), onResume(), onPause(), onStop(), onDestory()を それぞれoverrideしてログを仕込んで⾒る •

    実際にどんなログがでるか確認してみよう
  52. 開いてるところでonstと書き始めると候補がでるのでEnter

  53. 各メソッドにログを仕込む override fun onStart() { super.onStart() Log.d("yomuraya", "onStart") } ログを検索しやすいように

    わかりやすい⽂字を書いておく ログとしてメソッド名を書いておく
  54. ログの確認 Logcatを選択 第⼀引数のタグ名を⼊れる

  55. SharedPreferencesの使い⽅ val settings = getSharedPreferences("setting", Context.MODE_PRIVATE) val text = settings.getString("text",

    "") 設定を保存するファイル名 MODEは必ずPRIVATE 型ごとにgetメッドがある 設定を保存するキー名 値がなかったときの初期値
  56. Kotlinのフィールド // フィールドでは初期化が必要 var number: Int = 0 // 初期化しない=null許容であればクラス名のあとに?を付ける

    var number: Int? = null // あとで必ず初期化するから!というときはlateinit varを使う lateinit var number: Int // おまけ:遅延初期化をしたい場合は by lazy (valのみ) val number: Int by lazy { // 初期化処理を書く。ただし最初にアクセスした1回しか呼ばれない }
  57. 可視性 • フィールドやメンバメソッドはデフォルトで public • private はおなじクラスからしかアクセスできない • protected は

    private + ⼦クラス • internal は同じモジュールからアクセスできる • クラスやメソッドを継承は継承元に open がついたものしかできない ◦ Java的に⾔えばデフォルトは final 状態
  58. static • Javaとちがい、Kotlinにはstaticの概念はありません ◦ すべてのメソッドや変数はどこかのオブジェクトにひも付きます • 概念がないだけでstatic的なことはできます • object 宣⾔:プログラム中で唯⼀のインスタンスになる=シングルトン

    • companion object:クラスの中にシングルトンを持てる
  59. object 宣⾔ // object 宣⾔したものはそのものがインスタンスみたいなもの object DataProviderManager { fun registerDataProvider(provider:

    DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ... } // JavaでもKotlinでもクラスメソッドのように呼べる DataProviderManager.registerDataProvider(...)
  60. object 式 // objectとよく似たものでobject式がある // こちらは無名クラスを作るもの window.addMouseListener(object : MouseAdapter() {

    override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } })
  61. companion object class MyClass { // class 宣⾔の中に書く companion object

    { // メソッドやフィールドを定義できる val foo: String = "bar" fun create(): MyClass = MyClass() } } // 実体はクラス内にある1個のインスタンス(コンパニオンオブジェクト) val x = MyClass.Companion // コンパニオンオブジェクトは省略して書ける val instance = MyClass.create()
  62. textViewに値をset (初期値はお好みで) MainActivity.kt(1) class MainActivity : AppCompatActivity() { companion object

    { const val PREF_NAME = "settings" } private lateinit var settings: SharedPreferences override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) settings = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val textView = findViewById<TextView>(R.id.textView) textView.text = settings.getString("text", "DEFAULT!") } companion objectを使って 定数を表現 onCreate()で初期化するの でlateinit var 忘れずに初期化する
  63. MainActivity.kt(2) button.setOnClickListener { _ -> val inputText = editText.text.toString() textView.text

    = inputText settings.edit() .putString("text", inputText) .apply() } EditText.textはEditableクラ スなのでStringに変換 Editorクラスを取得して書き込み準備 書き込む型に応じてメソッドを変える 最後にapply()を忘れずに!
  64. はやくできた⼈は • Activityを閉じたときにテキストを保存してみよう ◦ 戻るボタンとホームボタンで閉じたときの違いを⾒てみよう • Activityを開いたときと閉じたときの時間を記録し、次開いたときに表⽰ してみよう ◦ 戻るボタンとホームボタンで閉じたときの違いを⾒てみよう

  65. 別画⾯を実装しよう

  66. 複数のActivityを連携させる • Androidは複数のActivityを使って画⾯遷移を実現できる • Activityはスタックで管理されていて、次々開いていくとどんどんスタッ クにたまる • 元のActivityに戻るためには、上にあるActivityを閉じていく必要がある • そのスタックもいろいろいじれるけど今回は割愛

  67. 2つ⽬のActivityを作ろう

  68. None
  69. None
  70. 課題4 • MainActivityに2つ⽬のActivityを呼び出すボタンを追加しよう • ボタンを押して2つ⽬のActivityを呼び出そう • MainActivityから2つ⽬のActivityにデータを渡そう

  71. None
  72. None
  73. MainActivity.kt val secondButton = findViewById<Button>(R.id.secondButton) secondButton.setOnClickListener { val intent =

    Intent(this, SecondActivity::class.java) startActivity(intent) } Activityを呼び出すにはIntentクラスに呼び出したいクラスを渡す startActivity()メソッドでintentで指定したActivityを呼び出す
  74. Intent • 他のActivityやServiceなどAndroidの他のコンポーネントへの指⽰を格納する クラス • Action:指⽰を出したコンポーネントに何をしてほしいか ◦ 例:VIEW, EDIT, DIAL

    • Data:データの位置を⽰すUri • Flag:主にシステム向きの属性(スタックには載せないで、など) • Extras:コンポーネントにわたす追加データ
  75. MainActivity.kt secondButton.setOnClickListener { val intent = Intent(this, SecondActivity::class.java) intent.putExtra("name", "Yohei

    Murayama") startActivity(intent) } putExtra()を使ってデータを渡す オーバーロードされてるのでどの型で もputExtra()でOK
  76. SecondActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) val nameTextView

    = findViewById<TextView>(R.id.nameTextView) val name: String? = intent.getStringExtra("name") nameTextView.text = name } Extraはnullを返すときがあるの で必ず?付きの型で受ける putExtra()と違い型別にメソッドが別れている TextView.textは仮にnullを⼊れても落ちない
  77. はやくできた⼈は • Extraを使っていろいろな値を渡してみよう • ヤフーのURLをブラウザアプリで開いてみよう ◦ 何かを開いてほしいときはACTION_VIEW

  78. リストを表⽰してみよう

  79. リストで表⽰しよう • 1画⾯に収まれきれない情報をスクロールして表⽰する • FacebookやInstagramなどでおなじみ • ただし1,000件も10,000件も⼀つの画⾯に⼊れるとメモリが⾜りない • ListViewやRecyclerViewを使ってメモリの節約

  80. RecyclerView • スクロールしてたくさんの要素を表⽰できるView • 画⾯に表⽰している分しかViewを持っていない ◦ 画⾯から消えたら”リサイクル”して他の要素に転⽤する • Viewのタイプを分けられる ◦

    ヘッダと内容など複数を同じリストに⼊れられる
  81. RecyclerViewに必要なクラス • RecyclerView ◦ リストを表⽰する場所 • RecyclerView.Adapter ◦ リストに表⽰するデータとRecyclerViewとの橋渡し •

    RecyclerView.ViewHolder ◦ リストに表⽰する要素のViewを保持している←リサイクルされるやつ • LayoutManager ◦ RecyclerViewに要素をどう並べるか決めるクラス(今回はLinearしか使わない)
  82. RecyclerViewの図式 RecyclerView ViewHolder ViewHolder ViewHolder ViewHolder ViewHolder ViewHolder LinearLayoutManager Adapter

    データのリスト 要素をどういうふうに並べるか を制御する 新しいViewHolderを作ったり 表⽰するViewHolderにデータをセットする
  83. 課題5 • XMLでRecyclerViewの中に出すレイアウトを作ろう • 2つめのActivityでRecyclerViewを表⽰しよう

  84. 新しいレイアウトを作ろう

  85. None
  86. None
  87. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50dp" android:orientation="vertical" android:gravity="center">

    <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" tools:text="11⽉"/> </LinearLayout> layout_width, layout_heightは必須 ⼦要素の表⽰位置を決める のはgravity 初めて使うidは”@+id/”から始める Layout Editorで表⽰するときだけ出したい ⽂字は”tools:text”に⼊れる ⽂字サイズはsp サイズの単位はdp
  88. 単位 dp • Density-independent Pixels • 端末のディスプレイによらず同じようなサイズを表現する • 横幅が1080だったり、720だったりしても同じような⾒え⽅になる •

    厳密に同じになるわけではない
  89. None
  90. レイアウトのgravity • android:gravity ◦ ⼦要素や内容がどの位置に配置するかを⽰す • android:layout_gravity ◦ ⾃分⾃⾝をどこに配置するかを⽰す

  91. ViewHolderクラスを作ろう

  92. None
  93. None
  94. ListItemViewHolder.kt class ListItemViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { val name: TextView? =

    itemView.findViewById(R.id.name) } コンストラクタの引数 クラスのextendsやimplementsは :の後ろ継承元を書く 親クラスのコンストラクタへ渡す引数 nullを返す可能せいがあるので?付き コンスタクタの引数は フィールドの初期化で使⽤可能
  95. RecyclerView.Adapterを作ろう

  96. None
  97. None
  98. None
  99. None
  100. RecyclerView.Adaperの3つメソッド • onCreateViewHolder() ◦ 新しいViewHolderが必要になったときにViewHolderを作る • getItemCount() ◦ 表⽰する要素の数を返す •

    onBindViewHolder() ◦ 表⽰するためにViewHolderに値をセットする
  101. MonthRecyclerViewAdapter.kt class MonthRecycleViewAdapter(private val months: List<String>): RecyclerView.Adapter<ListItemViewHolder>() { override fun

    onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.listitem, parent, false) return ListItemViewHolder(itemView) } override fun getItemCount(): Int { return months.size } override fun onBindViewHolder(holder: ListItemViewHolder, position: Int) { holder.name?.text = months[position] } }
  102. MonthRecyclerViewAdapter.onCreateViewHolder override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder { val

    itemView = LayoutInflater.from(parent.context) .inflate(R.layout.listitem, parent, false) return ListItemViewHolder(itemView) } いろいろな要素を表⽰するときにtypeを指定できる LayoutInflaterを使ってlistitem.xmlからViewを作る さきほど作ったViewHolderのインスタンスを返す
  103. MonthRecyclerViewAdapter.getItemCount class MonthRecycleViewAdapter(private val months: List<String>): RecyclerView.Adapter<ListItemViewHolder>() { override fun

    getItemCount(): Int { return months.size } 引数に可視性を書くとそのままフィールドになる リスト全体のサイズを返す
  104. MonthRecyclerViewAdapter.onBindViewHolder override fun onBindViewHolder(holder: ListItemViewHolder, position: Int) { holder.name?.text =

    months[position] } 表⽰しようとしているViewHolderと 何番⽬の要素かが渡される ?:は 変数がnullじゃないときに後ろのメソッ ドを実⾏する (nullのときは何もしない) Kotlinの仕様でListでも配列のようなアクセスが可能
  105. None
  106. RecyclerViewを実装しよう

  107. activity_second.xml <?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/recycler_view" android:layout_height="match_parent" android:layout_width="match_parent"

    tools:listitem="@layout/listitem" /> Layout Editorにテスト⽤の⼦要素を表⽰
  108. SecondActivity.kt val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) // ⾼さが固定の場合はtrueのほうがパフォーマンスがよくなる recyclerView.setHasFixedSize(true) // 縦⽅向のみにレイアウトする

    recyclerView.layoutManager = LinearLayoutManager(this) // 表⽰するリストの作成 val months = listOf("1⽉","2⽉","3⽉","4⽉","5⽉","6⽉", "7⽉","8⽉","9⽉","10⽉","11⽉","12⽉") // AdapterをRecyclerViewにセット recyclerView.adapter = MonthRecyclerViewAdapter(months)
  109. SecondActivity.kt val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) // ⾼さが固定の場合はtrueのほうがパフォーマンスがよくなる recyclerView.setHasFixedSize(true) // 縦⽅向のみにレイアウトする

    recyclerView.layoutManager = LinearLayoutManager(this) // 表⽰するリストの作成 val months = (1..12).map { "${it}⽉" } // AdapterをRecyclerViewにセット recyclerView.adapter = MonthRecyclerViewAdapter(months)
  110. SecondActivity.kt val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) // ⾼さが固定の場合はtrueのほうがパフォーマンスがよくなる recyclerView.setHasFixedSize(true) // 縦⽅向のみにレイアウトする

    recyclerView.layoutManager = LinearLayoutManager(this) // 表⽰するリストの作成 val months = (2004..2018).flatMap { y -> (1..12).map { m -> "${y}年${m}⽉"} } // AdapterをRecyclerViewにセット recyclerView.adapter = MonthRecyclerViewAdapter(months)
  111. None
  112. はやくできた⼈は • 表⽰するテキストを増やしてみよう • テキストと⼀緒に画像も出してみよう ◦ ヒント:ImageViewとDrawable • 表⽰するViewHolderの数を増やしてみよう ◦

    ヒント:RecyclerViewAdapter.getItemViewType()