Slide 1

Slide 1 text

Jetpack Composeで作る 楽しい金額入力画面! chuka

Slide 2

Slide 2 text

自己紹介 2 chuka モバイル開発本部 決済グループ 2024年入社 STORES 決済 Android の開発 普段は北海道からリモートワーク おいしいちゅうかがたべたい 𝕏:@YkOxc

Slide 3

Slide 3 text

STORES 決済 について 3 お店のキャッシュレスをかんたんに STORES 決済 アプリと決済端末だけで かんたんにキャッシュレス決済を導入 3種類の決済方法をサポート ・クレジットカード ・電子マネー ・QRコード

Slide 4

Slide 4 text

支払い金額を入力 STORES 決済 について 4 クレジットカード・電子マネーの場合

Slide 5

Slide 5 text

支払い金額を入力 STORES 決済 について 5 決済端末と通信 クレジットカード・電子マネーの場合

Slide 6

Slide 6 text

支払い金額を入力 STORES 決済 について 6 クレジットカードや 電子マネーでお支払い 決済端末と通信 クレジットカード・電子マネーの場合

Slide 7

Slide 7 text

金額入力画面 とは 7 ・オーナーさんが利用する画面 ・会計時に金額を入力する画面 ・金額表示とキーパッドがある

Slide 8

Slide 8 text

金額入力画面 とは 8 ・オーナーさんが利用する画面 ・会計時に金額を入力する画面 ・金額表示とキーパッドがある 楽しい金額入力画面??🤔

Slide 9

Slide 9 text

9 金額入力画面のここが楽しい!

Slide 10

Slide 10 text

金額入力画面のここが楽しい! 10 円形のエフェクトが楽しい!

Slide 11

Slide 11 text

金額入力画面のここが楽しい! 11 円形のエフェクトが楽しい! 波紋の広がり方が楽しい!

Slide 12

Slide 12 text

金額入力画面のここが楽しい! 12 円形のエフェクトが楽しい! 波紋の広がり方が楽しい! ドラッグに追従して楽しい!

Slide 13

Slide 13 text

金額入力画面のここが楽しい! 13 数字が飛んでいって楽しい! 円形のエフェクトが楽しい! 波紋の広がり方が楽しい! ドラッグに追従して楽しい!

Slide 14

Slide 14 text

金額入力画面のここが楽しい! 14 ・UI/UXにこだわっている ・アプリで1番楽しくて可愛い ・私は初見で30分触り続けた ・可愛くて大好き!

Slide 15

Slide 15 text

金額入力画面の現状と課題 15 ・入社当時Javaだった (今はKotlin移行済み!) ・Android ViewでのUI実装がわからない 🥲 ・最終更新実質5年前のバグなし伝説のコード ・とにかく実装が複雑で難解!

Slide 16

Slide 16 text

金額入力画面の現状と課題 16 変更を加えにくい! ・入社当時Javaだった (今はKotlin移行済み!) ・Android ViewでのUI実装がわからない 🥲 ・最終更新実質5年前のバグなし伝説のコード ・とにかく実装が複雑で難解!

Slide 17

Slide 17 text

Jetpack Composeとは 17 Androidの新しいUIツールキット 2021年7月に安定版リリース ・宣言的UIで直感的にわかりやすい ・Kotlinで記述できてコード量も削減 ・プレビュー機能などで開発速度UP

Slide 18

Slide 18 text

Jetpack Composeとは 18 STORES 決済 Android も、 Jetpack Compose移行に取り組んでいます! Androidの新しいUIツールキット 2021年7月に安定版リリース ・宣言的UIで直感的にわかりやすい ・Kotlinで記述できてコード量も削減 ・プレビュー機能などで開発速度UP

Slide 19

Slide 19 text

金額入力画面の現状と課題 19 ・入社当時Javaだった (今はKotlin移行済み!) ・Android ViewでのUI実装がわからない 🥲 ・最終更新実質5年前のバグなし伝説のコード ・とにかく実装が複雑で難解! 変更を加えにくい!

Slide 20

Slide 20 text

つくるか金額入力画面 20 楽しくて可愛くてお気に入りの金額入力画面 Jetpack Composeで実装してみよう!🚀

Slide 21

Slide 21 text

タイトル回収 21 Jetpack Composeで作る 楽しい金額入力画面! 金額入力画面が好きすぎて Jetpack Composeで実装を考えてみた話 ⚠ プロダクションコードには反映されていません

Slide 22

Slide 22 text

発表のゴール 22 ・STORES 決済 の金額入力画面の楽しさが伝わる ・Jetpack Composeでのジェスチャーに応じた アニメーションの実装がわかる

Slide 23

Slide 23 text

話すこと 23 円形のエフェクトが楽しい! 波紋の広がり方が楽しい! ドラッグに追従して楽しい!

Slide 24

Slide 24 text

実装の流れ 24 ① キーを1つ作ってみる ② 押下時のアニメーションを実装する ③ キーパッドらしく配置する ④ ドラッグ時のアニメーションを実装する

Slide 25

Slide 25 text

25 ① キーを1つ作ってみる ② 押下時のアニメーションを実装する ③ キーパッドらしく配置する ④ ドラッグ時のアニメーションを実装する キーを1個つくる

Slide 26

Slide 26 text

26 @Composable
 fun Key() {
 Box(
 modifier = Modifier
 .size(120.dp)
 .background(Color.LightGray),
 contentAlignment = Alignment.Center
 ) {
 Text(
 text = "1",
 style =
 TextStyle(
 fontSize = 28.sp,
 color = Color.DarkGray,
 ),
 )
 }
 }
 キーを1個つくる

Slide 27

Slide 27 text

27 @Composable
 fun Key() {
 Box(
 modifier = Modifier
 .size(120.dp)
 .background(Color.LightGray),
 contentAlignment = Alignment.Center
 ) {
 Text(
 text = "1",
 style =
 TextStyle(
 fontSize = 28.sp,
 color = Color.DarkGray,
 ),
 )
 }
 }
 キーを1個つくる

Slide 28

Slide 28 text

28 @Composable
 fun Key() {
 Box(
 modifier = Modifier
 .size(120.dp)
 .background(Color.LightGray),
 contentAlignment = Alignment.Center
 ) {
 Text(
 text = "1",
 style =
 TextStyle(
 fontSize = 28.sp,
 color = Color.DarkGray,
 ),
 )
 }
 }
 キーを1個つくる

Slide 29

Slide 29 text

29 @Composable
 fun Key() {
 Box(
 modifier = Modifier
 .size(120.dp)
 .background(Color.LightGray),
 contentAlignment = Alignment.Center
 ) {
 Text(
 text = "1",
 style =
 TextStyle(
 fontSize = 28.sp,
 color = Color.DarkGray,
 ),
 )
 }
 }
 キーを1個つくる

Slide 30

Slide 30 text

30 ① キーを1つ作ってみる ② 押下時のアニメーションを実装する ③ キーパッドらしく配置する ④ ドラッグ時のアニメーションを実装する 押下時のアニメーションの実装

Slide 31

Slide 31 text

押下時のアニメーションの実装 31 ① 色と文字サイズが変化 - グレーから青 - 押下時に文字が縮小 ② 円形のエフェクトが表示 - 外側から中心に向かって出現 - 中心から外側に向かって消失

Slide 32

Slide 32 text

押下時のアニメーションの実装 32 ① 色と文字サイズが変化 - グレーから青 - 押下時に文字が縮小 ② 円形のエフェクトが表示 - 外側から中心に向かって出現 - 中心から外側に向かって消失

Slide 33

Slide 33 text

押下時のアニメーションの実装① - 色と文字サイズ - 33 val transition = updateTransition(isSelectedState, label = "selected state")
 
 val fontSize by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 0.85f else 1f
 },
 label = "",
 )
 
 val fontColor by transition.animateColor(
 targetValueByState = { isSelected ->
 if (isSelected) Color.Blue else Color.DarkGray
 },
 label = "",
 )


Slide 34

Slide 34 text

押下時のアニメーションの実装① - 色と文字サイズ - 34 val transition = updateTransition(isSelectedState, label = "selected state")
 
 val fontSize by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 0.85f else 1f
 },
 label = "",
 )
 
 val fontColor by transition.animateColor(
 targetValueByState = { isSelected ->
 if (isSelected) Color.Blue else Color.DarkGray
 },
 label = "",
 )
 キー押下時(isSelectedがtrueのとき) 文字サイズを0.85倍 それ以外は1倍

Slide 35

Slide 35 text

押下時のアニメーションの実装① - 色と文字サイズ - 35 val transition = updateTransition(isSelectedState, label = "selected state")
 
 val fontSize by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 0.85f else 1f
 },
 label = "",
 )
 
 val fontColor by transition.animateColor(
 targetValueByState = { isSelected ->
 if (isSelected) Color.Blue else Color.DarkGray
 },
 label = "",
 )
 キー押下時(isSelectedがtrueのとき) 文字色をBlue それ以外はDarkGray

Slide 36

Slide 36 text

押下時のアニメーションの実装① - 色と文字サイズ - 36 Text(
 text = "1",
 modifier = Modifier.graphicsLayer {
 scaleX = fontSize
 scaleY = fontSize
 transformOrigin = TransformOrigin.Center
 },
 style =
 TextStyle(
 textMotion = TextMotion.Animated,
 fontSize = 28.sp,
 color = fontColor,
 ),
 )


Slide 37

Slide 37 text

押下時のアニメーションの実装① - 色と文字サイズ - 37 Text(
 text = "1",
 modifier = Modifier.graphicsLayer {
 scaleX = fontSize
 scaleY = fontSize
 transformOrigin = TransformOrigin.Center
 },
 style =
 TextStyle(
 textMotion = TextMotion.Animated,
 fontSize = 28.sp,
 color = fontColor,
 ),
 )


Slide 38

Slide 38 text

押下時のアニメーションの実装① - 色と文字サイズ - 38 Text(
 text = "1",
 modifier = Modifier.graphicsLayer {
 scaleX = fontSize
 scaleY = fontSize
 transformOrigin = TransformOrigin.Center
 },
 style =
 TextStyle(
 textMotion = TextMotion.Animated,
 fontSize = 28.sp,
 color = fontColor,
 ),
 )


Slide 39

Slide 39 text

押下時のアニメーションの実装 39 ① 色と文字サイズが変化 - グレーから青 - 押下時に文字が縮小 ② 円形のエフェクトが表示 - 外側から中心に向かって出現 - 中心から外側に向かって消失

Slide 40

Slide 40 text

押下時のアニメーションの実装② - 円形エフェクト - 40 val circleSize by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 100f else 120f
 },
 label = "",
 )
 val circleWidth by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 8f else 0f
 },
 label = "",
 )


Slide 41

Slide 41 text

押下時のアニメーションの実装② - 円形エフェクト - 41 val circleSize by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 100f else 120f
 },
 label = "",
 )
 val circleWidth by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 8f else 0f
 },
 label = "",
 )
 キー押下時(isSelectedがtrueのとき) 円の大きさを100 離したとき120

Slide 42

Slide 42 text

押下時のアニメーションの実装② - 円形エフェクト - 42 val circleSize by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 100f else 120f
 },
 label = "",
 )
 val circleWidth by transition.animateFloat(
 targetValueByState = { isSelected ->
 if (isSelected) 8f else 0f
 },
 label = "",
 )
 キー押下時(isSelectedがtrueのとき) 円の太さを8 離したとき0

Slide 43

Slide 43 text

押下時のアニメーションの実装② - 円形エフェクト - 43 transition.AnimatedVisibility(
 visible = { targetValue -> targetValue },
 enter = fadeIn(),
 exit = fadeOut(),
 ) {
 Box(
 modifier =
 Modifier
 .size(circleSize.dp)
 .border(
 width = circleWidth.dp,
 color = Color.Blue,
 shape = CircleShape,
 ),
 )
 }


Slide 44

Slide 44 text

押下時のアニメーションの実装② - 円形エフェクト - 44 transition.AnimatedVisibility(
 visible = { targetValue -> targetValue },
 enter = fadeIn(),
 exit = fadeOut(),
 ) {
 Box(
 modifier =
 Modifier
 .size(circleSize.dp)
 .border(
 width = circleWidth.dp,
 color = Color.Blue,
 shape = CircleShape,
 ),
 )
 }
 Modifier.border(shape = CircleShape) で 円形の枠線を描画

Slide 45

Slide 45 text

押下時のアニメーションの実装② - 円形エフェクト - 45 transition.AnimatedVisibility(
 visible = { targetValue -> targetValue },
 enter = fadeIn(),
 exit = fadeOut(),
 ) {
 Box(
 modifier =
 Modifier
 .size(circleSize.dp)
 .border(
 width = circleWidth.dp,
 color = Color.Blue,
 shape = CircleShape,
 ),
 )
 }
 targetValue(isSelected)に応じて アニメーションで表示・非表示

Slide 46

Slide 46 text

押下時のアニメーションの実装③ - ジェスチャーの判定 - 46 Box(
 // 背景色などの設定は省略 
 modifier =
 Modifier
 .pointerInput(Unit) {
 detectTapGestures(
 onPress = {
 isSelected = true
 // 指を離したら 
 tryAwaitRelease()
 isSelected = false
 },
 )
 },
 ) {
 // ① のTextと②のCircleを重ねて配置 
 }


Slide 47

Slide 47 text

Box(
 // 背景色などの設定は省略 
 modifier =
 Modifier
 .pointerInput(Unit) {
 detectTapGestures(
 onPress = {
 isSelected = true
 // 指を離したら 
 tryAwaitRelease()
 isSelected = false
 },
 )
 },
 ) {
 // ① のTextと②のCircleを重ねて配置 
 }
 押下時のアニメーションの実装③ - ジェスチャーの判定 - 47 detectTapGestures(onPress = {}) で 長押しのジェスチャーイベントを検知

Slide 48

Slide 48 text

押下時のアニメーションの実装 48 既存 できたもの

Slide 49

Slide 49 text

押下時のアニメーションの実装 49 circleWidth固定 circleWidthアニメーション 既存

Slide 50

Slide 50 text

50 ① キーを1つ作ってみる ② 押下時のアニメーションを実装する ③ キーパッドらしく配置する ④ ドラッグ時のアニメーションを実装する キーパッドらしくする

Slide 51

Slide 51 text

キーパッドらしくする 51 val keyPad =
 listOf(
 "7", "8", "9",
 "4", "5", "6",
 "1", "2", "3",
 "0", "00", "del",
 )
 
 LazyVerticalGrid(
 columns = GridCells.Fixed(3),
 ) {
 items(keyPad) { key ->
 Key(
 text = key,
 isSelected = selected
 )
 }
 }


Slide 52

Slide 52 text

キーパッドらしくする 52 val keyPad =
 listOf(
 "7", "8", "9",
 "4", "5", "6",
 "1", "2", "3",
 "0", "00", "del",
 )
 
 LazyVerticalGrid(
 columns = GridCells.Fixed(3),
 ) {
 items(keyPad) { key ->
 Key(
 text = key,
 isSelected = selected
 )
 }
 }
 ドラッグ時の実装をしよう!

Slide 53

Slide 53 text

53 ① キーを1つ作ってみる ② 押下時のアニメーションを実装する ③ キーパッドらしく配置する ④ ドラッグ時のアニメーションを実装する ドラッグ時のアニメーションの実装

Slide 54

Slide 54 text

ドラッグ時のアニメーションの実装 54 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ③ ①と②を組み合わせる

Slide 55

Slide 55 text

ドラッグ時のアニメーションの実装 55 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ③ ①と②を組み合わせる

Slide 56

Slide 56 text

ドラッグ時のアニメーションの実装① - ジェスチャーの検出 - 56 Modifier
 .pointerInput(Unit) {
 detectDragGestures(
 onDragStart = {
 // ドラッグ開始時
 },
 onDrag = {
 // ドラッグ中
 },
 onDragEnd = {
 // ドラッグ終了時
 },
 onDragCancel = {
 // 別のジェスチャが入力されたとき 
 },
 )
 },


Slide 57

Slide 57 text

ドラッグ時のアニメーションの実装① - ジェスチャーの検出 - 57 Modifier
 .pointerInput(Unit) {
 detectDragGestures(
 onDragStart = {
 // ドラッグ開始時
 },
 onDrag = {
 // ドラッグ中
 },
 onDragEnd = {
 // ドラッグ終了時
 },
 onDragCancel = {
 // 別のジェスチャが入力されたとき 
 },
 )
 },
 detectDragGesturesで ドラッグのジェスチャーイベントを検知

Slide 58

Slide 58 text

ドラッグ時のアニメーションの実装 58 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ③ ①と②を組み合わせる

Slide 59

Slide 59 text

ドラッグ時のアニメーションの実装② - ポインタの位置の検出 - 59 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 0
 1
 2
 3
 4
 5


Slide 60

Slide 60 text

60 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 0
 1
 2
 3
 4
 5
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 61

Slide 61 text

61 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 0
 1
 2
 3
 4
 5
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 62

Slide 62 text

0
 1
 2
 3
 4
 5
 62 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 63

Slide 63 text

0
 1
 2
 3
 4
 5
 63 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 120*120の正方形
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 64

Slide 64 text

0
 1
 2
 3
 4
 5
 64 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 itemInfo.offset(0,0)
 (350, 131) - (0,0) = (350,131)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 65

Slide 65 text

0
 1
 2
 3
 4
 5
 65 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 itemInfo.offset(120,0)
 (350, 131) - (120,0) = (230,131)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 66

Slide 66 text

0
 1
 2
 3
 4
 5
 66 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 itemInfo.offset(240,0)
 (350, 131) - (240,0) = (110,131)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 67

Slide 67 text

0
 1
 2
 3
 4
 5
 67 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 itemInfo.offset(0,120)
 (350, 131) - (0,120) = (350,11)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 68

Slide 68 text

0
 1
 2
 3
 4
 5
 68 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 itemInfo.offset(120,120)
 (350, 131) - (120,120) = (230,11)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 69

Slide 69 text

0
 1
 2
 3
 4
 5
 69 fun LazyGridState.gridItemKeyAtPosition(hitPoint: Offset): Int? =
 layoutInfo.visibleItemsInfo
 .find { itemInfo ->
 itemInfo.size.toIntRect().contains(hitPoint.round() - itemInfo.offset)
 }?.key as? Int
 Create a photo grid with multiselect behavior using Jetpack Compose https://medium.com/androiddevelopers/create-a-photo-grid-with-multiselect-behavior-using-jetpack-compose-9a8d588a9b63 hitPoint(350.2, 130.7)
 itemInfo.offset(240,120)
 (350, 131) - (240,120) = (110,11)
 ドラッグ時のアニメーションの実装② - ポインタの位置の検出 -

Slide 70

Slide 70 text

ドラッグ時のアニメーションの実装 70 ① ドラッグジェスチャーの検出 ② ポインタの位置の検出 ③ ①と②を組み合わせる

Slide 71

Slide 71 text

ドラッグ時のアニメーションの実装③ - ドラッグ中のポインタ検出 - 71 detectDragGestures(
 onDragStart = { offset ->
 lazyGridState.gridItemKeyAtPosition(offset)?.let { key ->
 pointer.value = key
 }
 },
 onDrag = { change, _ ->
 lazyGridState.gridItemKeyAtPosition(change.position)?.let { key ->
 pointer.value = key
 }
 },
 onDragEnd = {
 pointer.value = null
 },
 onDragCancel = {
 pointer.value = null
 },
 )


Slide 72

Slide 72 text

detectDragGestures(
 onDragStart = { offset ->
 lazyGridState.gridItemKeyAtPosition(offset)?.let { key ->
 pointer.value = key
 }
 },
 onDrag = { change, _ ->
 lazyGridState.gridItemKeyAtPosition(change.position)?.let { key ->
 pointer.value = key
 }
 },
 onDragEnd = {
 pointer.value = null
 },
 onDragCancel = {
 pointer.value = null
 },
 )
 72 ドラッグ開始時のoffsetを受けて 現在のキーをpointerに格納 ドラッグ時のアニメーションの実装③ - ドラッグ中のポインタ検出 -

Slide 73

Slide 73 text

detectDragGestures(
 onDragStart = { offset ->
 lazyGridState.gridItemKeyAtPosition(offset)?.let { key ->
 pointer.value = key
 }
 },
 onDrag = { change, _ ->
 lazyGridState.gridItemKeyAtPosition(change.position)?.let { key ->
 pointer.value = key
 }
 },
 onDragEnd = {
 pointer.value = null
 },
 onDragCancel = {
 pointer.value = null
 },
 )
 73 onDrag中は常に変更を受け取るので 都度最新のkeyを返す ドラッグ時のアニメーションの実装③ - ドラッグ中のポインタ検出 -

Slide 74

Slide 74 text

detectDragGestures(
 onDragStart = { offset ->
 lazyGridState.gridItemKeyAtPosition(offset)?.let { key ->
 pointer.value = key
 }
 },
 onDrag = { change, _ ->
 lazyGridState.gridItemKeyAtPosition(change.position)?.let { key ->
 pointer.value = key
 }
 },
 onDragEnd = {
 pointer.value = null
 },
 onDragCancel = {
 pointer.value = null
 },
 )
 74 Drag終了・キャンセル時はnull ドラッグ時のアニメーションの実装③ - ドラッグ中のポインタ検出 -

Slide 75

Slide 75 text

完成 75

Slide 76

Slide 76 text

完成 - 既存との比較 - 76

Slide 77

Slide 77 text

まとめ 77 ・完全に振る舞いから実装  → Jetpack Composeの知識だけで    既存に近い実装ができた! ・既存645行 / 移行後234行  → コード量が63%減少! ・ジェスチャーのハンドリングが難しい  → 上下のドラッグがうまく反応しないことも    長押しとドラッグが競合している可能性 ・金額入力画面やっぱり可愛くて楽しい  → 全画面もっと楽しく可愛くしたい!

Slide 78

Slide 78 text

最後に ・STORES 決済 の金額入力画面は楽しい! ・Jetpack Composeでのジェスチャーに応じた  アニメーションの実装ができた! 78  こだわりを持って  Jetpack Compose移行やっていきます💪