5年更新していなかったアプリが 海外バイヤーから数万ドルで 買収されそうになったので、 久しぶりにアプリを更新しようと思ったら 色々仕様が変わっていたので、 細々したよくやるやつをツラツラと話す話/RecentlyAndroidFunctions

Daec7e5cd5fae384eda88037d937343b?s=47 AAkira
November 21, 2019

5年更新していなかったアプリが 海外バイヤーから数万ドルで 買収されそうになったので、 久しぶりにアプリを更新しようと思ったら 色々仕様が変わっていたので、 細々したよくやるやつをツラツラと話す話/RecentlyAndroidFunctions

You can check gif slide in Google Photo.
https://is.gd/nx5hJi

I talked this in the meetup of thanksgiving android presented by m3.
https://m3-engineer.connpass.com/event/152923/

Daec7e5cd5fae384eda88037d937343b?s=128

AAkira

November 21, 2019
Tweet

Transcript

  1. 3.
  2. 7.

    ことの発端 $ 40,000 = 40,000 * 109 = ¥ 4,360,000

    発表時だけの秘密♡ 発表時だけの秘密♡ 発表時だけの秘密♡
  3. 15.
  4. 16.

    まとめ • 買って何をするのか具体的に教えてくれないので、
 ちょっと怪しい • この人はバイヤーなので、裏に誰かがいる? • TOP5 countries [!>-"?]

    で
 50,000ユーザぐらいいれば4~500万円を手にすることが出来る
 (可能性がある) 発表時だけの秘密♡
  5. 28.

    いろいろやったリスト • QuickSettingsTiles(通知エリアのタイルボタン) • Palette • WindowManager • バックグラウンド •

    SharedElement • Transition animation(Explode, 角丸ピッ) • WindowInsets • AnimatedVectorDrawable • BottomAppBar • ViewPager2 (Infinity Loop, ひょっこりはん)
  6. 29.

    いろいろやったリスト • QuickSettingsTiles(通知エリアのタイルボタン) • Palette • WindowManager • バックグラウンド •

    SharedElement • Transition animation(Explode, 角丸ピッ) • WindowInsets • AnimatedVectorDrawable • BottomAppBar • ViewPager2 (Infinity Loop, ひょっこりはん) 上3つは話す予定
 あとは挙手制?
  7. 33.

    Quick Settings Tile class MyTileService: TileService(){ override fun onClick() {

    super.onClick() // ΫϦοΫ࣌ʹݺ͹ΕΔ } override fun onTileRemoved() { super.onTileRemoved() // ฤूը໘͔ΒλΠϧ࡟আ࣌ʹݺ͹ΕΔ } override fun onTileAdded() { super.onTileAdded() // ฤूը໘͔ΒλΠϧ௥Ճ࣌ʹݺ͹ΕΔ } override fun onStartListening() { super.onStartListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕දࣔ͞Εͨ࣌ } override fun onStopListening() { super.onStopListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕ݟ͑ͳ͘ͳͬͨ࣌ } }
  8. 34.

    class MyTileService: TileService(){ override fun onClick() { super.onClick() // ΫϦοΫ࣌ʹݺ͹ΕΔ

    } override fun onTileRemoved() { super.onTileRemoved() // ฤूը໘͔ΒλΠϧ࡟আ࣌ʹݺ͹ΕΔ } override fun onTileAdded() { Quick Settings Tile
  9. 35.

    override fun onClick() { super.onClick() // ΫϦοΫ࣌ʹݺ͹ΕΔ } override fun

    onTileRemoved() { super.onTileRemoved() // ฤूը໘͔ΒλΠϧ࡟আ࣌ʹݺ͹ΕΔ } override fun onTileAdded() { super.onTileAdded() // ฤूը໘͔ΒλΠϧ௥Ճ࣌ʹݺ͹ΕΔ } override fun onStartListening() { Quick Settings Tile
  10. 36.

    override fun onTileRemoved() { super.onTileRemoved() // ฤूը໘͔ΒλΠϧ࡟আ࣌ʹݺ͹ΕΔ } override fun

    onTileAdded() { super.onTileAdded() // ฤूը໘͔ΒλΠϧ௥Ճ࣌ʹݺ͹ΕΔ } override fun onStartListening() { super.onStartListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕දࣔ͞Εͨ࣌ } override fun onStopListening() { Quick Settings Tile
  11. 37.

    override fun onTileAdded() { super.onTileAdded() // ฤूը໘͔ΒλΠϧ௥Ճ࣌ʹݺ͹ΕΔ } override fun

    onStartListening() { super.onStartListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕දࣔ͞Εͨ࣌ } override fun onStopListening() { super.onStopListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕ݟ͑ͳ͘ͳͬͨ࣌ } } Quick Settings Tile ここでState Checkした方が良い
  12. 38.

    override fun onTileAdded() { super.onTileAdded() // ฤूը໘͔ΒλΠϧ௥Ճ࣌ʹݺ͹ΕΔ } override fun

    onStartListening() { super.onStartListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕දࣔ͞Εͨ࣌ qsTile?.state = if (isFeatureRunning()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE qsTile?.updateTile() } override fun onStopListening() { super.onStopListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕ݟ͑ͳ͘ͳͬͨ࣌ } Quick Settings Tile 通知エリアの状態表示
  13. 39.

    override fun onTileAdded() { super.onTileAdded() // ฤूը໘͔ΒλΠϧ௥Ճ࣌ʹݺ͹ΕΔ } override fun

    onStartListening() { super.onStartListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕දࣔ͞Εͨ࣌ } override fun onStopListening() { super.onStopListening() // ௨஌ΤϦΞ಺ͷλΠϧ͕ݟ͑ͳ͘ͳͬͨ࣌ } } Quick Settings Tile
  14. 41.

    Quick Settings Tile <service android:name=".MyTileService" android:enabled="true" android:exported="true" android:icon="@drawable/ic_tile_icon" android:label="DisplayedTitle" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">

    <intent-filter> <action android:name="android.service.quicksettings.action.QS_TILE" /> </intent-filter> </service> 作成したService class AndroidManifest.xml
  15. 42.

    Quick Settings Tile <service android:name=".MyTileService" android:enabled="true" android:exported="true" android:icon="@drawable/ic_tile_icon" android:label="DisplayedTitle" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">

    <intent-filter> <action android:name="android.service.quicksettings.action.QS_TILE" /> </intent-filter> </service> QuickSettingsに表示するアイコン AndroidManifest.xml
  16. 43.

    Quick Settings Tile <service android:name=".MyTileService" android:enabled="true" android:exported="true" android:icon="@drawable/ic_tile_icon" android:label="DisplayedTitle" android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">

    <intent-filter> <action android:name="android.service.quicksettings.action.QS_TILE" /> </intent-filter> </service> QuickSettingsに表示するラベル AndroidManifest.xml
  17. 46.

    Palette import androidx.palette.graphics.Palette
 
 Palette.from(bitmap).generate { val lightPalette = it?.lightVibrantSwatch

    ?: return@generate val mutedPalette = it.vibrantSwatch ?: return@generate appBarContent.setStatusBarScrimColor(lightPalette.rgb) appBarContent.setContentScrimColor(mutedPalette.rgb) }
  18. 47.

    import androidx.palette.graphics.Palette
 
 Palette.from(bitmap).generate { val lightPalette = it?.lightVibrantSwatch ?:

    return@generate val mutedPalette = it.vibrantSwatch ?: return@generate appBarContent.setStatusBarScrimColor(lightPalette.rgb) appBarContent.setContentScrimColor(mutedPalette.rgb) } Palette 非同期で取得(中身はAsyncTask)
  19. 48.

    import androidx.palette.graphics.Palette
 Palette.from(bitmap).generate { val lightPalette = it?.lightVibrantSwatch ?: return@generate

    val mutedPalette = it.vibrantSwatch ?: return@generate appBarContent.setStatusBarScrimColor(lightPalette.rgb) appBarContent.setContentScrimColor(mutedPalette.rgb) } Palette.from(bitmap).generate() Palette 同期取得
  20. 49.

    import androidx.palette.graphics.Palette
 
 Palette.from(bitmap).generate { val lightPalette = it?.lightVibrantSwatch ?:

    return@generate val mutedPalette = it.vibrantSwatch ?: return@generate appBarContent.setStatusBarScrimColor(lightPalette.rgb) appBarContent.setContentScrimColor(mutedPalette.rgb) } Palette
  21. 50.

    import androidx.palette.graphics.Palette
 
 Palette.from(bitmap).generate { val lightPalette = it?.lightVibrantSwatch ?:

    return@generate val mutedPalette = it.vibrantSwatch ?: return@generate appBarContent.setStatusBarScrimColor(lightPalette.rgb) appBarContent.setContentScrimColor(mutedPalette.rgb) } Palette 異なる鮮やかさが取れる
  22. 53.

    画面にフィルターを重ねる <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> •Lollipopまではこれだけ •Marshmallow~ +Runtime permissionが必要 val intent

    = Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName") ) startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE) •Pie~ ManifestのPermission名変更 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  23. 58.

    画面にフィルターを重ねる val view: View = View(this) view.setBackgroundColor(Color.argb(170, 0, 0, 0))

    val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT ) params.windowAnimations = android.R.style.Animation_Toast windowManager.addView(view, params)
  24. 59.

    画面にフィルターを重ねる val view: View = View(this) view.setBackgroundColor(Color.argb(170, 0, 0, 0))

    val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT ) params.windowAnimations = android.R.style.Animation_Toast windowManager.addView(view, params) Layer指定
  25. 60.

    画面にフィルターを重ねる val view: View = View(this) view.setBackgroundColor(Color.argb(170, 0, 0, 0))

    val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT ) params.windowAnimations = android.R.style.Animation_Toast windowManager.addView(view, params) 8.0以降では落ちる
  26. 61.

    画面にフィルターを重ねる val view: View = View(this) view.setBackgroundColor(Color.argb(170, 0, 0, 0))

    val params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT ) params.windowAnimations = android.R.style.Animation_Toast windowManager.addView(view, params) Flag指定 ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE)
  27. 64.

    いろいろやったリスト • QuickSettingsTiles(通知エリアのタイルボタン) • Palette • WindowManager • バックグラウンド •

    SharedElement • Transition animation(Explode, 角丸ピッ) • WindowInsets • AnimatedVectorDrawable • BottomAppBar • ViewPager2 (Infinity Loop, ひょっこりはん)
  28. 68.

    • アプリのウィンドウが、フォアグラウンドのアクティビティであるなど、画面上に表示されている。 • アプリのアクティビティが、フォアグラウンド タスクのバック スタックにある。 • アプリのアクティビティが、最近使ったアプリ画面の既存タスクのバックスタックにある。 • ごく最近開始した、このアプリのアクティビティがある。

    • ごく最近、このアプリがアクティビティで finish() を呼び出した。これは、finish() が呼び出されたときに、このアプリのアクティビティがフォアグラウンドにあった、またはフォアグラウンド タス クのバック スタックにあった場合のみに適用されます。 • システムによってバインドされたサービスがこのアプリにある。この条件は次のサービスにのみ適用されます(UI の起動が必要となる場合があります)。 AccessibilityService、AutofillService、 CallRedirectionService、HostApduService、InCallService、TileService、VoiceInteractionService、および VrListenerService。 • このアプリには、表示されている別のアプリによってバインドされたサービスがある。バックグラウンドのアプリが正常にアクティビティを開始できるようにするため、そのサービスにバインドされ ているアプリは表示されたままでなければなりません。 • このアプリがシステムから通知 PendingIntent を受信している。サービスやブロードキャスト受信プログラムのインテントが保留中の場合は、保留中のインテントが送信されてから数秒間アプリは アクティビティを開始できます。 • 表示されている別のアプリから送信された PendingIntent をこのアプリが受信している。 • アプリがシステム ブロードキャストを受信し、そのための UI を起動することになっている。その例として、ACTION_NEW_OUTGOING_CALL と SECRET_CODE_ACTION があります。アプリは、ブ ロードキャストが送信されてから数秒間はアクティビティを開始できます。 • アプリが CompanionDeviceManager API によって付属ハードウェア端末に関連付けられている。この API により、アプリはペア設定した端末でユーザーが実行するアクションに反応して、アクティ ビティを開始できます。 • アプリが端末所有者モードで実行されている端末ポリシー コントローラである。使用例には、完全管理対象エンタープライズ端末だけでなく、デジタル サイネージやキオスクなどの専用端末も含ま れています。 • ユーザーによってアプリに SYSTEM_ALERT_WINDOW パーミッションが付与されている。 バックグラウンドからの起動条件 https://developer.android.com/guide/components/activities/background-starts
  29. 69.

    •アプリのウィンドウが、フォアグラウンドのアクティビティであるなど、画面上に表示されている。 •アプリのアクティビティが、フォアグラウンド タスクのバック スタックにある。 •アプリのアクティビティが、最近使ったアプリ画面の既存タスクのバックスタックにある。 •ごく最近開始した、このアプリのアクティビティがある。 •ごく最近、このアプリがアクティビティで finish() を呼び出した。これは、finish() が呼び出されたと

    きに、このアプリのアクティビティがフォアグラウンドにあった、またはフォアグラウンド タスクの バック スタックにあった場合のみに適用されます。 •システムによってバインドされたサービスがこのアプリにある。この条件は次のサービスにのみ適用さ れます(UI の起動が必要となる場合があります)。 AccessibilityService、AutofillService、 CallRedirectionService、HostApduService、InCallService、TileService、VoiceInteractionService、 および VrListenerService。 バックグラウンドからの起動条件
  30. 70.

    •システムによってバインドされたサービスがこのアプリにある。この条件は次のサービスにのみ適用さ れます(UI の起動が必要となる場合があります)。 AccessibilityService、AutofillService、 CallRedirectionService、HostApduService、InCallService、TileService、VoiceInteractionService、 および VrListenerService。 •このアプリには、表示されている別のアプリによってバインドされたサービスがある。バックグラウン ドのアプリが正常にアクティビティを開始できるようにするため、そのサービスにバインドされている アプリは表示されたままでなければなりません。

    •このアプリがシステムから通知 PendingIntent を受信している。サービスやブロードキャスト受信プロ グラムのインテントが保留中の場合は、保留中のインテントが送信されてから数秒間アプリはアクティ ビティを開始できます。 •表示されている別のアプリから送信された PendingIntent をこのアプリが受信している。 •アプリがシステム ブロードキャストを受信し、そのための UI を起動することになっている。その例と して、ACTION_NEW_OUTGOING_CALL と SECRET_CODE_ACTION があります。アプリは、ブロード キャストが送信されてから数秒間はアクティビティを開始できます。 バックグラウンドからの起動条件 https://developer.android.com/guide/components/activities/background-starts
  31. 73.

    <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" app:startDestination="@id/navigationMainFragment" > <fragment android:id="@+id/navigationHogeFragment"

    android:name="HogeFragment" android:label="HogeFragment" > <argument android:name="arg1" app:argType="integer" /> <argument android:name="arg2" app:argType="integer" /> </fragment> </navigation> Shared Element navigation.xml
  32. 75.

    Shared Element ViewCompat.setTransitionName(containerLayout, TRANSITION_KEY_CONTAINER) ViewCompat.setTransitionName(previewImage, TRANSITION_KEY_IMAGE) ViewCompat.setTransitionName(titleText, TRANSITION_KEY_TITLE) findNavController().navigate( R.id.navigationHogeFragment,

    HogeFragmentArgs(hogeArgs1, hogeArgs2).toBundle(), null, FragmentNavigatorExtras( containerLayout to containerLayout.transitionName, previewImage to previewImage.transitionName, titleText to titleText.transitionName ) ) 遷移元
  33. 76.

    Shared Element ViewCompat.setTransitionName(containerLayout, TRANSITION_KEY_CONTAINER) ViewCompat.setTransitionName(previewImage, TRANSITION_KEY_IMAGE) ViewCompat.setTransitionName(titleText, TRANSITION_KEY_TITLE) findNavController().navigate( R.id.navigationHogeFragment,

    HogeFragmentArgs(hogeArgs1, hogeArgs2).toBundle(), null, FragmentNavigatorExtras( containerLayout to containerLayout.transitionName, previewImage to previewImage.transitionName, titleText to titleText.transitionName ) ) いつも通りKeyをセット(xmlでも良い) 遷移元
  34. 77.

    Shared Element findNavController().navigate( R.id.navigationHogeFragment, HogeFragmentArgs(hogeArgs1, hogeArgs2).toBundle(), null, FragmentNavigatorExtras( containerLayout to

    containerLayout.transitionName, previewImage to previewImage.transitionName, titleText to titleText.transitionName ) ) Destination Id 遷移元
  35. 78.

    Shared Element findNavController().navigate( R.id.navigationHogeFragment, HogeFragmentArgs(hogeArgs1, hogeArgs2).toBundle(), null, FragmentNavigatorExtras( containerLayout to

    containerLayout.transitionName, previewImage to previewImage.transitionName, titleText to titleText.transitionName ) ) Safe args 遷移元
  36. 79.

    Shared Element findNavController().navigate( R.id.navigationHogeFragment, HogeFragmentArgs(hogeArgs1, hogeArgs2).toBundle(), null, FragmentNavigatorExtras( containerLayout to

    containerLayout.transitionName, previewImage to previewImage.transitionName, titleText to titleText.transitionName ) ) NavOptions 遷移元
  37. 80.

    Shared Element findNavController().navigate( R.id.navigationHogeFragment, HogeFragmentArgs(hogeArgs1, hogeArgs2).toBundle(), null, FragmentNavigatorExtras( containerLayout to

    containerLayout.transitionName, previewImage to previewImage.transitionName, titleText to titleText.transitionName ) ) ここ大事! 今まで通りTransition nameを設定する 遷移元
  38. 81.

    Shared Element 遷移先 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    ViewCompat.setTransitionName(destContainerLayout, TRANSITION_KEY_CONTAINER) ViewCompat.setTransitionName(destPreviewImage, TRANSITION_KEY_IMAGE) ViewCompat.setTransitionName(destTitleText, TRANSITION_KEY_TITLE) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = TransitionSet() sharedElementReturnTransition = TransitionSet() }
  39. 82.

    Shared Element 遷移先 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    ViewCompat.setTransitionName(destContainerLayout, TRANSITION_KEY_CONTAINER) ViewCompat.setTransitionName(destPreviewImage, TRANSITION_KEY_IMAGE) ViewCompat.setTransitionName(destTitleText, TRANSITION_KEY_TITLE) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = TransitionSet() sharedElementReturnTransition = TransitionSet() } onCreateで設定 カスタムTransitionにしたい場合はここを変更する
  40. 83.

    sharedElementEnterTransition = TransitionSet() sharedElementReturnTransition = TransitionSet() } Shared Element 遷移先

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ViewCompat.setTransitionName(destContainerLayout, TRANSITION_KEY_CONTAINER ViewCompat.setTransitionName(destPreviewImage, TRANSITION_KEY_IMAGE) ViewCompat.setTransitionName(destTitleText, TRANSITION_KEY_TITLE) } いつも通りKeyをセット(xmlでも良い)
  41. 84.

    sharedElementEnterTransition = TransitionSet() sharedElementReturnTransition = TransitionSet() } Shared Element 遷移先

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) postponeEnterTransition() destContainerLayout.doOnPreDraw { startPostponedEnterTransition() } ViewCompat.setTransitionName(destContainerLayout, TRANSITION_KEY_C ViewCompat.setTransitionName(destPreviewImage, TRANSITION_KEY_IMAG ViewCompat.setTransitionName(destTitleText, TRANSITION_KEY_TITLE) } RecyclerViewとかを使っていると動かない時がある 描画まで待つ
  42. 85.

    sharedElementEnterTransition = TransitionSet() sharedElementReturnTransition = TransitionSet() } Shared Element 遷移先

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) postponeEnterTransition() destContainerLayout.doOnPreDraw { startPostponedEnterTransition() } ViewCompat.setTransitionName(destContainerLayout, TRANSITION_KEY_C ViewCompat.setTransitionName(destPreviewImage, TRANSITION_KEY_IMAG ViewCompat.setTransitionName(destTitleText, TRANSITION_KEY_TITLE) } 描画されたらTransition開始
  43. 88.

    Transition Animation 遷移先 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition

    = TransitionSet() sharedElementReturnTransition = TransitionSet() } ここをカスタムしていく
  44. 91.

    Transition Animation private fun createSharedElementTransition(duration: Long): Transition { return transitionTogether

    { this.duration = duration interpolator = FAST_OUT_SLOW_IN this += SharedFade() this += ChangeImageTransform() this += ChangeBounds() this += ChangeTransform() } } operator fun TransitionSet.plusAssign(transition: Transition) { addTransition(transition) }
  45. 92.

    Transition Animation private fun createSharedElementTransition(duration: Long): Transition { return transitionTogether

    { this.duration = duration interpolator = FAST_OUT_SLOW_IN this += SharedFade() this += ChangeImageTransform() this += ChangeBounds() this += ChangeTransform() } } operator fun TransitionSet.plusAssign(transition: Transition) { addTransition(transition) } custom operator
  46. 93.

    Transition Animation private fun createSharedElementTransition(duration: Long): Transition { return transitionTogether

    { this.duration = duration interpolator = FAST_OUT_SLOW_IN this += SharedFade() this += ChangeImageTransform() this += ChangeBounds() this += ChangeTransform() } } operator fun TransitionSet.plusAssign(transition: Transition) { addTransition(transition) } こんな感じで書ける
  47. 94.

    private fun createSharedElementTransition(duration: Long): Transition { return transitionTogether { this.duration

    = duration interpolator = FAST_OUT_SLOW_IN addTransition(SharedFade()) addTransition(ChangeImageTransform()) addTransition(ChangeBounds()) addTransition(ChangeTransform()) } } Transition Animation operator fun TransitionSet.plusAssign(transition: Transition) { addTransition(transition) }
  48. 99.

    View Pager2 (Loop) class MyAdapter( private val itemData: List<Data> )

    : RecyclerView.Adapter<MyViewHolder>() { override fun getItemCount(): Int = Int.MAX_VALUE override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val data = itemData[position % itemData.size] ... } } RecyclerAdapter
  49. 100.

    View Pager2 (Loop) class MyAdapter( private val itemData: List<Data> )

    : RecyclerView.Adapter<MyViewHolder>() { override fun getItemCount(): Int = Int.MAX_VALUE override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val data = itemData[position % itemData.size] ... } } Maxにする RecyclerAdapter
  50. 101.

    View Pager2 (Loop) class MyAdapter( private val itemData: List<Data> )

    : RecyclerView.Adapter<MyViewHolder>() { override fun getItemCount(): Int = Int.MAX_VALUE override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val data = itemData[position % itemData.size] ... } } positionを配列幅でmod RecyclerAdapter
  51. 102.

    View Pager2 (Loop) viewPager.also { it.adapter = adapter val center

    = Int.MAX_VALUE / 2 val start = center - (center % items.size) it.setCurrentItem(start, false) } Activitym, Fragment側
  52. 103.

    viewPager.also { it.adapter = adapter val center = Int.MAX_VALUE /

    2 val start = center - (center % items.size) it.setCurrentItem(start, false) } View Pager2 (Loop) Activitym, Fragment側 先頭を中心にする
  53. 104.

    viewPager.also { it.adapter = adapter val center = Int.MAX_VALUE /

    2 val start = center - (center % items.size) it.setCurrentItem(start, false) } View Pager2 (Loop) Activitym, Fragment側 スワイプの上限
  54. 107.

    /> View Pager2 (ひょっこりはん) <dimen name="page_margin">4dp</dimen> <dimen name="page_offset">64dp</dimen> <dimen name="page_margin_and_offset">68dp</dimen>

    <com.google.android.material.card.MaterialCardView android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginStart="@dimen/page_margin_and_offset" android:layout_marginEnd="@dimen/page_margin_and_offset" app:cardCornerRadius="@dimen/item_card_radius" >
  55. 108.

    android:orientation="horizontal" /> View Pager2 (ひょっこりはん) <dimen name="page_margin">4dp</dimen> <dimen name="page_offset">64dp</dimen> <dimen

    name="page_margin_and_offset">68dp</dimen> <com.google.android.material.card.MaterialCardView android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginStart="@dimen/page_margin_and_offset" android:layout_marginEnd="@dimen/page_margin_and_offset" app:cardCornerRadius="@dimen/item_card_radius" >
  56. 111.

    viewpager.offscreenPageLimit = 2 val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.page_margin) val offsetPx =

    resources.getDimensionPixelOffset(R.dimen.page_offset)
 viewpager.setPageTransformer(CompositePageTransformer().apply { addTransformer { page, position -> val offset = position * (2 * offsetPx + pageMarginPx) page.translationX = -offset val scale = 1 - (abs(position) / 6) page.scaleX = scale page.scaleY = scale } }) android:orientation="horizontal" /> View Pager2 (ひょっこりはん)
  57. 112.

    viewpager.offscreenPageLimit = 2 val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.page_margin) val offsetPx =

    resources.getDimensionPixelOffset(R.dimen.page_offset)
 viewpager.setPageTransformer(CompositePageTransformer().apply { addTransformer { page, position -> val offset = position * (2 * offsetPx + pageMarginPx) page.translationX = -offset val scale = 1 - (abs(position) / 6) page.scaleX = scale page.scaleY = scale } }) android:orientation="horizontal" /> View Pager2 (ひょっこりはん) 左右のアイテム描画
  58. 113.

    viewpager.offscreenPageLimit = 2 val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.page_margin) val offsetPx =

    resources.getDimensionPixelOffset(R.dimen.page_offset)
 viewpager.setPageTransformer(CompositePageTransformer().apply { addTransformer { page, position -> val offset = position * (2 * offsetPx + pageMarginPx) page.translationX = -offset val scale = 1 - (abs(position) / 6) page.scaleX = scale page.scaleY = scale } }) android:orientation="horizontal" /> View Pager2 (ひょっこりはん) Swipe時のアニメーションを自由に作れる
  59. 120.

    Bottom app bar <com.google.android.material.bottomappbar.BottomAppBar android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:fabAlignmentMode="center" app:navigationIcon="@drawable/ic_menu_white" />

    <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:menu="@menu/menu_navigation" /> •Bottom App Bar •Bottom Navigation View
  60. 121.

    <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:menu="@menu/menu_navigation" /> Bottom app bar <com.google.android.material.bottomappbar.BottomAppBar

    android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:fabAlignmentMode="center" app:navigationIcon="@drawable/ic_menu_white" /> •Bottom App Bar •Bottom Navigation View xmlでアイコンセット出来る
  61. 122.

    <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:menu="@menu/menu_navigation" /> Bottom app bar <com.google.android.material.bottomappbar.BottomAppBar

    android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:fabAlignmentMode="center" app:navigationIcon="@drawable/ic_menu_white" /> •Bottom App Bar •Bottom Navigation View fabを食い込ませる
  62. 123.

    <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:menu="@menu/menu_navigation" /> Bottom app bar <com.google.android.material.bottomappbar.BottomAppBar

    android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:fabAlignmentMode="end" app:navigationIcon="@drawable/ic_menu_white" /> •Bottom App Bar •Bottom Navigation View endにすると端にずらせる(startは不可)
  63. 124.

    <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_anchor="@id/bottomBar" /> Bottom app bar <com.google.android.material.bottomappbar.BottomAppBar

    android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:fabAlignmentMode="end" app:navigationIcon="@drawable/ic_menu_white" />
  64. 125.

    <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_anchor="@id/bottomBar" /> Bottom app bar <com.google.android.material.bottomappbar.BottomAppBar

    android:id="@+id/bottomBar" android:layout_width="match_parent" android:layout_height="@dimen/bottom_bar_height" app:fabAlignmentMode="end" app:navigationIcon="@drawable/ic_menu_white" /> fabをBottomAppBarに紐付ける
  65. 127.

    class MyBottomSheetFragment : BottomSheetDialogFragment() { companion object { fun newInstance():

    BottomSheetNavigationFragment = BottomSheetNavigationFragment() } ... } Bottom app bar bottomBar.setNavigationOnClickListener { MyBottomSheetFragment.newInstance() .show(supportFragmentManager, TAG_BOTTOM_SHEET) } MyBottomSheetFragment.kt MainActivity.kt
  66. 128.

    class MyBottomSheetFragment : BottomSheetDialogFragment() { companion object { fun newInstance():

    BottomSheetNavigationFragment = BottomSheetNavigationFragment() } ... } Bottom app bar bottomBar.setNavigationOnClickListener { MyBottomSheetFragment.newInstance() .show(supportFragmentManager, TAG_BOTTOM_SHEET) } MyBottomSheetFragment.kt MainActivity.kt これだけで呼び出せる