Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up
for free
Jetpack Compose ことはじめ / the beginning of Jetpack Compose
kenken
August 24, 2019
Programming
2
1.9k
Jetpack Compose ことはじめ / the beginning of Jetpack Compose
kenken
August 24, 2019
Tweet
Share
More Decks by kenken
See All by kenken
tkhs0604
0
440
Other Decks in Programming
See All in Programming
ken3ypa
0
160
inoue2002
0
270
kubode
0
200
standfm
0
240
hanhan1978
0
290
yshrsmz
1
460
osyo
1
370
manfredsteyer
PRO
0
270
anchorcable
1
120
tommykw
1
340
thatjeffsmith
0
430
nanimonodemonai
2
1.4k
Featured
See All Featured
samlambert
237
9.9k
bkeepers
52
4.1k
marktimemedia
6
330
mongodb
23
3.8k
danielanewman
200
20k
kneath
294
39k
philhawksworth
192
8.8k
aarron
258
36k
philhawksworth
190
17k
lynnandtonic
271
16k
pauljervisheath
196
15k
deanohume
295
27k
Transcript
Jetpack Compose ことはじめ @tkhs0604 #kotlinfest Kotlin Fest 2019 @ 東京コンファレンスセンター・品川
• 高橋 健太 ◦ kenken | @tkhs0604 ◦ https://tkhs0604.hatenablog.com •
Gunosy Inc. • SI営業→エンジニア(iOS/Android/Web)→Androidエンジニア • アカペラ ◦ 最近ORICON NEWSに少しだけ載りました 自己紹介
本セッションの目標 • Jetpack Composeのさわりを理解する 本日のアジェンダ • Jetpack Compose とは •
サンプルアプリ • コンポーネントの作成方法 ◦ @Composable ◦ @Model 本セッションの目標 & 本日のアジェンダ
Jetpack Compose とは
• Google I/O 2019 • Android Jetpack • Kotlin •
宣言的UI ◦ cf. Flutter、React Native、Vue.js、SwiftUI • Pre-alpha版 (2019/8/23時点) ◦ ⚠大きく変更が入る可能性があります Jetpack Compose とは
サンプルアプリ
ToDoアプリ • タスクをリスト形式で表示 • アイテムまたはチェックボックスを タップすると、対応するタスクの状態 (完了 or 未完了)が切り替わる
コンポーネントの作成方法
@Composable & @Model @Composable • 関数に付与することで、コンポーネントとして認識される @Model • クラスに付与することで、コンポーネント内で状態を扱うためのクラ スとして使用可能になる
(※このままではただのクラス) • オブジェクトを+state関数に渡してStateオブジェクトを得る • Stateオブジェクトの内容が更新されると、フレームワーク側でUIの 再描画が行われる
ToDoアプリ class SampleActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?)
{ super.onCreate(savedInstanceState) setContent { ToDoApp() } } } • Activity.setContentView() のような役割 • この中でコンポーネントを組み立てると、 フレームワークが最適に描画処理を行う • UIはAndroidCraneView内のCanvasに描画 される
ToDoAppコンポーネント @Composable fun ToDoApp() { // 20個のサンプルデータ。 val tasks =
(1..20).map { Task(id = it, description = "Task No.$it", isCompleted = false) } MaterialTheme { VerticalScroller { Surface { Column { ToDoList(tasks = tasks) } } } } } data class Task( val id: Int, val description: String, val isCompleted: Boolean )
ToDoAppコンポーネント @Composable fun ToDoApp() { // 20個のサンプルデータ。 val tasks =
(1..20).map { Task(id = it, description = "Task No.$it", isCompleted = false) } MaterialTheme { VerticalScroller { Surface { Column { ToDoList(tasks = tasks) } } } } } • @Composableアノテーションを付与 した関数はコンポーネントと認識される • コンポーネントをネストする形で組み立てる ので、UIの階層構造がコードで表現される
ToDoAppコンポーネント @Composable fun ToDoApp() { // 20個のサンプルデータ。 val tasks =
(1..20).map { Task(id = it, description = "Task No.$it", isCompleted = false) } MaterialTheme { VerticalScroller { Surface { Column { ToDoList(tasks = tasks) } } } } } • テーマを設定するためのコンポーネント • Material Designのテーマが設定される (はず)
ToDoAppコンポーネント @Composable fun ToDoApp() { // 20個のサンプルデータ。 val tasks =
(1..20).map { Task(id = it, description = "Task No.$it", isCompleted = false) } MaterialTheme { VerticalScroller { Surface { Column { ToDoList(tasks = tasks) } } } } } • 子コンポーネントを垂直方向にスクロール可能 にするためのコンポーネント • これがないと基本的にスクロールできない (子コンポーネントがリスト系でも )
ToDoAppコンポーネント @Composable fun ToDoApp() { // 20個のサンプルデータ。 val tasks =
(1..20).map { Task(id = it, description = "Task No.$it", isCompleted = false) } MaterialTheme { VerticalScroller { Surface { Column { ToDoList(tasks = tasks) } } } } } • タップ時のRipple Effectを表現するための コンポーネント • これがないとチェックボックスなどの タッチフィードバックが表現できない (※何なら現状はクラッシュするw )
ToDoAppコンポーネント @Composable fun ToDoApp() { // 20個のサンプルデータ。 val tasks =
(1..20).map { Task(id = it, description = "Task No.$it", isCompleted = false) } MaterialTheme { VerticalScroller { Surface { Column { ToDoList(tasks = tasks) } } } } } • 子コンポーネントを垂直方向にリスト表示する ためのコンポーネント (cf. Row) • ここではToDoとDividerを交互にリスト表示 している @Composable fun ToDoList(tasks: List<Task>) { tasks.forEach { ToDo(it) Divider( color = Color.LightGray, height = 1.dp ) } }
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } } @Model data class TaskModel( var description: String, var isCompleted: Boolean )
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } } @Model data class TaskModel( var description: String, var isCompleted: Boolean ) • @Modelアノテーションを付与した TaskModelオブジェクトを+state関数に 渡してStateオブジェクトを得る
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } }
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } }
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } }
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } }
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } }
ToDoコンポーネント @Composable fun ToDo(task: Task) { val state = +state
{ TaskModel(description = task.description, isCompleted = task.isCompleted) } Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) { Padding(padding = EdgeInsets(all = 20.dp)) { Row(crossAxisAlignment = CrossAxisAlignment.Start) { Checkbox( checked = state.value.isCompleted, onCheckedChange = { state.value.isCompleted = it } ) WidthSpacer(width = 10.dp) Text( text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}" ) } } } } • stateのプロパティを更新することにより、フレー ムワーク側でUIの再描画が行われる 状態をトグルする チェックボックスの状態変 更を反映する
Pros. • ソースコードからUIが直感的に理解できる • kt/javaファイル、layout.xml、attrs.xml、drawable.xmlに 別個に書いていた内容を集約できる Cons. • ネスト地獄 •
Paddingとかの設定までコンポーネントで行うのは個人的には つらい (例えば、Textのプロパティとして渡したい) 所感
まだPre-alpha版なので…
Thank you! \ Follow me on Twitter /