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

DroidKaigi 2020: System UIをコントロールして、 画面を最大限に生かしたアプリを構築する / Build apps that make the best uses of device screens by controlling System UI

Sato Shun
April 02, 2020

DroidKaigi 2020: System UIをコントロールして、 画面を最大限に生かしたアプリを構築する / Build apps that make the best uses of device screens by controlling System UI

端末の画面を最大限に活かし、より良い体験を提供するために、System UI(Status Bar、Navigation Bar)を考慮することは非常に重要です。
しかし、Android Q、Q以前などでのバージョンごとの差異、ノッチ対応、画面構成によっては、WindowInsetsを取得する必要があるなど、考慮しなければならない点が多くあります。

この発表では、最初にSystem UIをコントロールするために抑えておくべき要素を、実際の挙動を見ながら、1つ1つ理解していきます。
具体的には、次のことを説明します。

- fitsSystemWindowsの挙動について
- ThemeからSystem UIの設定をする
- Navigation Component(Single Activity)との兼ね合い
- WindowInsetsを取り、動的にSystem UIを調整する
- FloatingActionButtonとの兼ね合い
- systemUiVisibilityについて
- ノッチ端末(cutout)への対応

最後に、実践的な例として、Google I/OなどのアプリのSystem UIをどのようにすれば構築出来るかを説明します。

System UIの基礎 〜 実践までを理解することで、画面を最大限に生かした没入感のあるアプリを実装出来るようになることを目指します。

Sato Shun

April 02, 2020
Tweet

More Decks by Sato Shun

Other Decks in Technology

Transcript

  1. DroidKaigi
    2020
    Sato Shun

    GitHub: satoshun

    Twitter: @stsn_jp

    CyberAgent, Inc.
    System UIΛίϯτϩʔϧͯ͠ɺ
    ը໘Λ࠷େݶʹੜ͔ͨ͠ΞϓϦΛߏங͢Δ

    View full-size slide

  2. ͜ͷൃදͷલఏ
    • API 21Ҏ߱Λ૝ఆ͍ͯ͠·͢

    • ͜ͷൃදͰ͸ɺAPI 21͕API 1ʹͳΓ·͢

    View full-size slide

  3. • Edge to Edgeͱ͸?

    • Edge to EdgeΛͳͥ΍Δ͔?

    • Edge to EdgeΛͲͷΑ͏ʹ΍Δ͔ʁ

    • ΧελϜView͔ΒֶͿ

    • ଞͷΞϓϦ͔ΒֶͿ

    View full-size slide

  4. Edge to Edgeͱ͸?

    View full-size slide

  5. • ը໘શମΛίϯςϯπྖҬʹ

    • System UIͷഎޙʹίϯςϯπ
    Λඳը
    https://medium.com/androiddevelopers/gesture-navigation-going-edge-to-edge-812f62e4e83e

    View full-size slide

  6. Edge to EdgeΛͳͥ΍Δ͔?

    View full-size slide

  7. Edge to EdgeΛͳͥ΍Δ͔?
    • ը໘શମΛ࢖͏͜ͱͰɺΑΓ຅ೖײͷ͋ΔΞϓϦΛߏஙͰ͖
    Δ

    • AndroidϓϥοτϑΥʔϜશମͷྲྀΕ

    • API 29(Q)Ҏ߱Ͱ͸ɺڧ͘નΊ͍ͯΔ

    View full-size slide

  8. Edge to EdgeΛͲͷΑ͏ʹ΍Δ͔

    View full-size slide

  9. Edge to EdgeΛͲͷΑ͏ʹ΍Δ͔
    • SystemUiVisibility

    • System UIͷഎܠ৭

    • fitsSystemWindows

    • WindowInsets

    View full-size slide

  10. SystemUiVisibility ͱ͸?
    • System UIʢstatus bar / navigation barʣͷݟͨ໨ɺৼΔ෣͍
    Λมߋ͢Δ

    View full-size slide

  11. SystemUiVisibility ͱ͸?
    • System UIʢstatus bar / navigation barʣͷݟͨ໨ɺৼΔ෣͍
    Λมߋ͢Δ

    • Immersive modeʢ຅ೖϞʔυʣ

    View full-size slide

  12. SystemUiVisibility ͱ͸?
    • System UIʢstatus bar / navigation barʣͷݟͨ໨ɺৼΔ෣͍
    Λมߋ͢Δ

    • Immersive modeʢ຅ೖϞʔυʣ

    View full-size slide

  13. SystemUiVisibility ͱ͸?
    • System UIʢstatus bar / navigation barʣͷݟͨ໨ɺৼΔ෣͍
    Λมߋ͢Δ

    • Immersive modeʢ຅ೖϞʔυʣ

    View full-size slide

  14. SystemUiVisibility ͱ͸?
    • System UIʢstatus bar / navigation barʣͷݟͨ໨ɺৼΔ෣͍
    Λมߋ͢Δ

    • Immersive modeʢ຅ೖϞʔυʣ

    • status bar / navigation barͷഎޙʹඳը

    View full-size slide

  15. SystemUiVisibility ͱ͸?
    • System UIʢstatus bar / navigation barʣͷݟͨ໨ɺৼΔ෣͍
    Λมߋ͢Δ

    • Immersive modeʢ຅ೖϞʔυʣ

    • status bar / navigation barͷഎޙʹඳը

    View full-size slide

  16. status bar
    navigation bar

    View full-size slide




  17. γεςϜ͕ViewΛ
    ૠೖ͢Δ

    View full-size slide

  18. paddingTopɺmarginBottom͕
    ηοτ͞Ε͍ͯΔ

    View full-size slide

  19. paddingTopɺmarginBottom͕
    ηοτ͞Ε͍ͯΔ

    View full-size slide

  20. paddingTopɺmarginBottom͕
    ηοτ͞Ε͍ͯΔ
    -> ίϯςϯπΛඳը͢Δ͜ͱ͸ग़དྷͳ͍

    View full-size slide

  21. paddingTopɺmarginBottom͕
    ηοτ͞Ε͍ͯΔ
    -> ίϯςϯπΛඳը͢Δ͜ͱ͸ग़དྷͳ͍
    ͜ͷpaddingTopɺmarginBottomͷ஋Λ
    ্ॻ͖͢Δඞཁ͕͋Δ

    View full-size slide

  22. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    View full-size slide

  23. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    View full-size slide

  24. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    View full-size slide

  25. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    View full-size slide

  26. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    View full-size slide

  27. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

    View full-size slide

  28. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  29. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  30. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  31. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  32. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  33. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  34. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  35. SystemUiVisibility·ͱΊ
    • SYSTEM_UI_FLAG_LAYOUT_FULLSCREENɺ
    SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    • 2ͭΛ૊Έ߹ΘͤΔ͜ͱͰɺmarginɺpaddingΛ0ʹઃఆ͢Δ͜ͱ
    ͕ग़དྷΔ

    • ίϯςϯπΛSystem UIͷഎޙʹඳը͢Δ͜ͱ͕ग़དྷΔ

    • SYSTEM_UI_FLAG_LAYOUT_STABLE΋෇͚ͨ΄͏͕ྑ͍

    • ৄ͘͠͸ɺDroidKaigi2020 ৄղ WindowInsetsΛݟ͍ͯͩ͘͞

    View full-size slide

  36. System UIͷഎܠ৭

    View full-size slide

  37. window.statusBarColor = …
    #B3FFFFFF

    View full-size slide

  38. window.statusBarColor = …
    #B3FFFFFF

    View full-size slide

  39. view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    true

    View full-size slide

  40. view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    true

    View full-size slide

  41. API23+
    view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    true

    View full-size slide

  42. API23+
    API23ະຬͰ͸ɺ
    Lightʢ໌Δ͍ʣ৭Λstatus barͷ
    എܠ৭ʹऔΔͱࢹೝੑ͕ஶ͘͠
    ௿Լ͢Δ͜ͱΛҙຯ͢Δ
    view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    true

    View full-size slide

  43. window.navigationBarColor = …
    #B3FFFFFF

    View full-size slide

  44. window.navigationBarColor = …
    #B3FFFFFF

    View full-size slide

  45. view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
    true

    View full-size slide

  46. view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
    true

    View full-size slide

  47. API27+
    view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
    true

    View full-size slide

  48. API27+
    API27ະຬͰ͸ɺ
    Lightʢ໌Δ͍ʣ৭Λnavigationͷ
    എܠ৭ʹऔΔͱࢹೝੑ͕ஶ͘͠
    ௿Լ͢Δ͜ͱΛҙຯ͢Δ
    view.systemUiVisibility = ...
    View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
    true

    View full-size slide

  49. API29+͔ΒtransparentΛ
    ࢦఆͨ͠৔߹ʹɺ
    ࣗಈతʹഎܠ৭Λઃఆͯ͘͠ΕΔ
    Α͏ʹͳͬͨ
    transparent

    View full-size slide

  50. API29+͔ΒtransparentΛ
    ࢦఆͨ͠৔߹ʹɺ
    ࣗಈతʹഎܠ৭Λઃఆͯ͘͠ΕΔ
    Α͏ʹͳͬͨ
    transparent
    false
    false

    View full-size slide

  51. transparent
    API29+͔ΒtransparentΛ
    ࢦఆͨ͠৔߹ʹɺ
    ࣗಈతʹഎܠ৭Λઃఆͯ͘͠ΕΔ
    Α͏ʹͳͬͨ
    false
    false

    View full-size slide

  52. System UIഎܠ৭ͷ·ͱΊ
    • API23+: windowLightStatusBarͷ௥Ճ

    • API27+: windowLightNavigationBarͷ௥Ճ

    • API29+: enforceNavigationBarContrastɺ
    enforceStatusBarContrastͷ௥Ճ

    • transparentͷͱ͖ɺ͍͍ײ͡ʹഎܠ৭Λௐ੔ͯ͘͠ΕΔ

    View full-size slide

  53. fitsSystemWindows

    View full-size slide

  54. fitsSystemWindowsͱ͸?
    • System UIΛߟྀͯ͠ɺΞϓϦέʔγϣϯίϯςϯπΛ഑ஔͯ͠
    ͘ΕΔ

    • σϑΥϧτͷಈ࡞ͱɺΧελϚΠζͨ͠ಈ࡞͕͋Δ

    • ೉͍͠ଐੑͷ1ͭʢݸਓతͳײ૝ʣ

    View full-size slide

  55. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  56. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION

    View full-size slide

  57. view.systemUiVisibility =
    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
    ্෦ɺԼ෦ͷmarginɺpadding͕θϩʹͳΔͷͰɺ
    ཁૉ͕ΊΓࠐΜͰ͠·͏

    View full-size slide







  58. fitsSystemWindows͸ɺ
    paddingTopɺpaddingBottomΛઃఆ͢Δ

    View full-size slide

  59. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ






    View full-size slide







  60. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ

    View full-size slide







  61. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    View
    View View
    View View

    View full-size slide







  62. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    View
    View View
    View View

    View full-size slide







  63. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    View
    View View
    View View

    View full-size slide







  64. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    View
    View View
    View View

    View full-size slide







  65. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    View
    View View
    View View

    View full-size slide







  66. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    View
    View View
    View View

    View full-size slide







  67. ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ
    fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ

    View full-size slide







  68. fitsSystemWindows͸σϑΥϧτͰɺ
    ਂ͞༏ઌ୳ࡧΛ͠ɺݟ͔ͭͬͨ࠷ॳͷΈ༗ޮ
    ͜ͷfitsSystemWindows͸ແࢹ͞ΕΔ

    View full-size slide

  69. fitsSystemWindows·ͱΊ
    • σϑΥϧτͷڍಈ

    • System UIʹඃΒͳ͍Α͏ʹɺpaddingTopɺpaddingBottomΛ
    ઃఆ͢Δ

    • ਂ͞༏ઌ୳ࡧͰݟ͔ͭͬͨɺ࠷ॳͷViewͷΈ༗ޮʹͳΔ
    ΋ͬͱॊೈʹ࢖͍͍ͨཁٻ

    View full-size slide

  70. WindowInsets

    View full-size slide

  71. WindowInsetsͱ͸?
    • System UIͷ෯ɺ઎ΊΔྖҬ

    • WindowInsetsΛ࢖͏͜ͱͰɺfitsSystemWindowsΛΑΓॊೈ
    ʹΧελϚΠζͯ͠࢖͏͜ͱ͕ग़དྷΔ

    View full-size slide

  72. view.setOnApplyWindowInsetsListener { v, insets ->
    insets
    }

    View full-size slide

  73. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  74. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  75. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  76. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }
    class WindowInsets {
    Insets getSystemWindowInsets()
    Insets getStableInsets()
    Insets getSystemGestureInsets()
    Insets getMandatorySystemGestureInsets()
    Insets getTappableElementInsets()
    DisplayCutout getDisplayCutout()
    }

    View full-size slide

  77. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  78. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }
    child.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  79. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }
    child.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  80. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets.consumeSystemWindowInsets()
    }
    child.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  81. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets.consumeSystemWindowInsets()
    }
    child.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  82. view.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets.consumeSystemWindowInsets()
    }
    child.setOnApplyWindowInsetsListener { v, insets ->
    v.updatePadding(
    top = insets.systemWindowInsetTop,
    bottom = insets.systemWindowInsetBottom
    )
    insets
    }

    View full-size slide

  83. public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    try {
    mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
    if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
    return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this,
    insets);
    } else {
    return onApplyWindowInsets(insets);
    }
    } finally {
    mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
    }
    }

    View full-size slide

  84. public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    try {
    mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
    if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
    return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this,
    insets);
    } else {
    return onApplyWindowInsets(insets);
    }
    } finally {
    mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
    }
    }

    View full-size slide

  85. public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
    if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
    return insets.consumeSystemWindowInsets();
    }
    } else {
    if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
    return insets.consumeSystemWindowInsets();
    }
    }
    return insets;
    }

    View full-size slide

  86. public WindowInsets onApplyWindowInsets(WindowInsets insets) {
    if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
    if (fitSystemWindows(insets.getSystemWindowInsetsAsRect())) {
    return insets.consumeSystemWindowInsets();
    }
    } else {
    if (fitSystemWindowsInt(insets.getSystemWindowInsetsAsRect())) {
    return insets.consumeSystemWindowInsets();
    }
    }
    return insets;
    }
    σϑΥϧτͷfitsSystemWindowsͷ৔߹ɺInsetsΛফඅ͢Δ

    View full-size slide

  87. public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    try {
    mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
    if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
    return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this,
    insets);
    } else {
    return onApplyWindowInsets(insets);
    }
    } finally {
    mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
    }
    }

    View full-size slide

  88. public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
    try {
    mPrivateFlags3 |= PFLAG3_APPLYING_INSETS;
    if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
    return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this,
    insets);
    } else {
    return onApplyWindowInsets(insets);
    } finally {
    mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS;
    }
    }

    View full-size slide

  89. public void setOnApplyWindowInsetsListener(OnApplyWindowInsetsListener listener) {
    getListenerInfo().mOnApplyWindowInsetsListener = listener;
    }
    Listener͕ొ࿥͞Ε͍ͯΔͱɺ
    fitsSystemWindowsͷಈ࡞ΛΧελϚΠζ͢Δ
    ͜ͱ͕ग़དྷΔ
    view.setOnApplyWindowInsetsListener { v, insets ->

    }

    View full-size slide

  90. WindowInsets·ͱΊ
    • System UIͷ෯ɺྖҬΛऔಘͰ͖Δ

    • ফඅͨ͠৔߹ɺͦΕҎ߱ʹWindowInsets͕఻ൖ͠ͳ͍

    • σϑΥϧτͷfitsSystemWindowsͷ৔߹ɺඞͣফඅ͢Δ

    • setOnApplyWindowListener͔ΒɺfitsSystemWindowsͷσ
    ϑΥϧτͷڍಈΛม͑Δ͜ͱ͕Մೳ

    View full-size slide

  91. ΧελϜView͔ΒֶͿ

    View full-size slide







  92. setOnApplyWindowListenerΛ࢖ͬͯɺ

    σϑΥϧτͷڍಈΛม͍͑ͯΔ

    ·ͨɺWindowInsets͸ফඅ͍ͯ͠ͳ͍

    View full-size slide

  93. private void setupForInsets() {
    if (ViewCompat.getFitsSystemWindows(this)) {
    if (mApplyWindowInsetsListener == null) {
    mApplyWindowInsetsListener =
    new androidx.core.view.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsetsCompat onApplyWindowInsets(View v,
    WindowInsetsCompat insets) {
    return setWindowInsets(insets);
    }
    };
    }
    // First apply the insets listener
    ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);

    View full-size slide

  94. private void setupForInsets() {
    if (ViewCompat.getFitsSystemWindows(this)) {
    if (mApplyWindowInsetsListener == null) {
    mApplyWindowInsetsListener =
    new androidx.core.view.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsetsCompat onApplyWindowInsets(View v,
    WindowInsetsCompat insets) {
    return setWindowInsets(insets);
    }
    };
    }
    // First apply the insets listener
    ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);

    View full-size slide

  95. private void setupForInsets() {
    if (ViewCompat.getFitsSystemWindows(this)) {
    if (mApplyWindowInsetsListener == null) {
    mApplyWindowInsetsListener =
    new androidx.core.view.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsetsCompat onApplyWindowInsets(View v,
    WindowInsetsCompat insets) {
    return setWindowInsets(insets);
    }
    };
    }
    // First apply the insets listener
    ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);

    View full-size slide

  96. private void setupForInsets() {
    if (ViewCompat.getFitsSystemWindows(this)) {
    if (mApplyWindowInsetsListener == null) {
    mApplyWindowInsetsListener =
    new androidx.core.view.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsetsCompat onApplyWindowInsets(View v,
    WindowInsetsCompat insets) {
    return setWindowInsets(insets);
    }
    };
    }
    // First apply the insets listener
    ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);

    View full-size slide

  97. final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
    if (!ObjectsCompat.equals(mLastInsets, insets)) {
    mLastInsets = insets;
    // Now dispatch to the Behaviors
    insets = dispatchApplyWindowInsetsToBehaviors(insets);
    requestLayout();
    }
    return insets;
    }

    View full-size slide







  98. final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
    if (!ObjectsCompat.equals(mLastInsets, insets)) {
    mLastInsets = insets;
    // Now dispatch to the Behaviors
    insets = dispatchApplyWindowInsetsToBehaviors(insets);
    requestLayout();
    }
    return insets;
    }

    View full-size slide

  99. if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
    && !ViewCompat.getFitsSystemWindows(child)) {
    parent.left += mLastInsets.getSystemWindowInsetLeft();
    parent.top += mLastInsets.getSystemWindowInsetTop();
    parent.right -= mLastInsets.getSystemWindowInsetRight();
    parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
    }
    final Rect out = acquireTempRect();
    GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
    child.getMeasuredHeight(), parent, out, layoutDirection);
    child.layout(out.left, out.top, out.right, out.bottom);
    private void layoutChild(…)

    View full-size slide

  100. if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
    && !ViewCompat.getFitsSystemWindows(child)) {
    parent.left += mLastInsets.getSystemWindowInsetLeft();
    parent.top += mLastInsets.getSystemWindowInsetTop();
    parent.right -= mLastInsets.getSystemWindowInsetRight();
    parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
    }
    final Rect out = acquireTempRect();
    GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
    child.getMeasuredHeight(), parent, out, layoutDirection);
    child.layout(out.left, out.top, out.right, out.bottom);
    private void layoutChild(…)

    View full-size slide

  101. if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
    && !ViewCompat.getFitsSystemWindows(child)) {
    parent.left += mLastInsets.getSystemWindowInsetLeft();
    parent.top += mLastInsets.getSystemWindowInsetTop();
    parent.right -= mLastInsets.getSystemWindowInsetRight();
    parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
    }
    final Rect out = acquireTempRect();
    GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
    child.getMeasuredHeight(), parent, out, layoutDirection);
    child.layout(out.left, out.top, out.right, out.bottom);






    View full-size slide

  102. if (mLastInsets != null && ViewCompat.getFitsSystemWindows(this)
    && !ViewCompat.getFitsSystemWindows(child)) {
    parent.left += mLastInsets.getSystemWindowInsetLeft();
    parent.top += mLastInsets.getSystemWindowInsetTop();
    parent.right -= mLastInsets.getSystemWindowInsetRight();
    parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
    }
    final Rect out = acquireTempRect();
    GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
    child.getMeasuredHeight(), parent, out, layoutDirection);
    child.layout(out.left, out.top, out.right, out.bottom);






    View full-size slide

  103. ViewCompat.setOnApplyWindowInsetsListener(
    this,
    new androidx.core.view.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
    return onWindowInsetChanged(insets);
    }
    });
    class AppBarLayout

    View full-size slide

  104. ViewCompat.setOnApplyWindowInsetsListener(
    this,
    new androidx.core.view.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
    return onWindowInsetChanged(insets);
    }
    });

    View full-size slide

  105. WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
    WindowInsetsCompat newInsets = null;
    if (ViewCompat.getFitsSystemWindows(this)) {
    newInsets = insets;
    }

    }

    View full-size slide

  106. WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
    WindowInsetsCompat newInsets = null;
    if (ViewCompat.getFitsSystemWindows(this)) {
    newInsets = insets;
    }

    }

    View full-size slide

  107. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY
    && ViewCompat.getFitsSystemWindows(this)
    && shouldOffsetFirstChild()) {
    int newHeight = getMeasuredHeight();
    switch (heightMode) {
    case MeasureSpec.AT_MOST:
    newHeight =
    MathUtils.clamp(
    getMeasuredHeight() + getTopInset(), 0,
    MeasureSpec.getSize(heightMeasureSpec));
    break;

    View full-size slide

  108. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY
    && ViewCompat.getFitsSystemWindows(this)
    && shouldOffsetFirstChild()) {
    int newHeight = getMeasuredHeight();
    switch (heightMode) {
    case MeasureSpec.AT_MOST:
    newHeight =
    MathUtils.clamp(
    getMeasuredHeight() + getTopInset(), 0,
    MeasureSpec.getSize(heightMeasureSpec));
    break;

    View full-size slide

  109. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY
    && ViewCompat.getFitsSystemWindows(this)
    && shouldOffsetFirstChild()) {
    int newHeight = getMeasuredHeight();
    switch (heightMode) {
    case MeasureSpec.AT_MOST:
    newHeight =
    MathUtils.clamp(
    getMeasuredHeight() + getTopInset(), 0,
    MeasureSpec.getSize(heightMeasureSpec));
    break;







    View full-size slide

  110. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY
    && ViewCompat.getFitsSystemWindows(this)
    && shouldOffsetFirstChild()) {
    int newHeight = getMeasuredHeight();
    switch (heightMode) {
    case MeasureSpec.AT_MOST:
    newHeight =
    MathUtils.clamp(
    getMeasuredHeight() + getTopInset(), 0,
    MeasureSpec.getSize(heightMeasureSpec));
    break;







    View full-size slide

  111. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY
    && ViewCompat.getFitsSystemWindows(this)
    && shouldOffsetFirstChild()) {
    int newHeight = getMeasuredHeight();
    switch (heightMode) {
    case MeasureSpec.AT_MOST:
    newHeight =
    MathUtils.clamp(
    getMeasuredHeight() + getTopInset(), 0,
    MeasureSpec.getSize(heightMeasureSpec));
    break;







    View full-size slide

  112. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (heightMode != MeasureSpec.EXACTLY
    && ViewCompat.getFitsSystemWindows(this)
    && shouldOffsetFirstChild()) {
    int newHeight = getMeasuredHeight();
    switch (heightMode) {
    case MeasureSpec.AT_MOST:
    newHeight =
    MathUtils.clamp(
    getMeasuredHeight() + getTopInset(), 0,
    MeasureSpec.getSize(heightMeasureSpec));
    break;







    ΋ͱ΋ͱͷAppbarLayoutߴ͞ + status bar ͷߴ͞

    View full-size slide

  113. ΧελϜView·ͱΊ
    • CoordinatorLayoutɺAppbarLayoutɺBottomAppbar͸ɺͦΕ
    ͧΕWindowInsetΛಠࣗͰղऍ͢Δ

    • ଞʹ΋ɺCollapsingToolbarLayoutɺDrawerLayoutͱ͔΋ͦ͏

    • ͜ΕΒͷView͸ɺfitsSystemWindowsΛtrueʹࢦఆ͢Δͱɺ
    ͍͍ײ͡ʹInsetΛղऍͯ͘͠ΕΔ

    • fitsSystemWindowsࢦఆͯ͠΋ɺফඅ͠ͳ͔ͬͨΓ͢Δ

    View full-size slide

  114. ଞͷΞϓϦ͔ΒֶͿ

    View full-size slide

  115. ςʔϚΛߏங͢Δ

    View full-size slide

  116. APIϨϕϧΛߟྀ͢Δඞཁ͕͋Δ
    • API23+

    • windowLightStatusBar

    • API27+

    • windowLightNavigationBar

    • API29+

    • enforceStatusBarContrast

    • enforceNavigationBarContrast

    View full-size slide

  117. APIϨϕϧΛߟྀ͢Δඞཁ͕͋Δ
    • API23+

    • windowLightStatusBar

    • API27+

    • windowLightNavigationBar

    • API29+

    • enforceStatusBarContrast

    • enforceNavigationBarContrast
    21 22 23 24 25 26 27 28 29

    View full-size slide

  118. Dev Summit 2019ΞϓϦ
    https://github.com/google/iosched/pull/333

    View full-size slide

  119. 21 22 23 24 25 26 27 28 29

    View full-size slide

  120. 21 22 23 24 25 26 27 28 29

    View full-size slide

  121. windowLightStatusBar ✗
    windowLightNavigationBar ✗
    statusBarColor #66000000
    navigationBarColor #66000000
    API21 22

    View full-size slide

  122. windowLightStatusBar true
    windowLightNavigationBar ✗
    statusBarColor transparent
    navigationBarColor #66000000
    API23 24 25 26

    View full-size slide

  123. windowLightStatusBar true
    windowLightNavigationBar ✗
    statusBarColor transparent
    navigationBarColor #66000000
    API23 24 25 26

    View full-size slide

  124. windowLightStatusBar true
    windowLightNavigationBar true
    statusBarColor transparent
    navigationBarColor #B3FFFFFF
    API27 28

    View full-size slide

  125. windowLightStatusBar true
    windowLightNavigationBar true
    statusBarColor transparent
    navigationBarColor #B3FFFFFF
    API27 28

    View full-size slide

  126. windowLightStatusBar true
    windowLightNavigationBar true
    statusBarColor transparent
    navigationBarColor transparent
    API29

    View full-size slide

  127. windowLightStatusBar ✗ / false
    windowLightNavigationBar ✗ / false
    statusBarColor transparent
    navigationBarColor #B3000000
    API21 ~ 28

    View full-size slide

  128. windowLightStatusBar ✗ / false
    windowLightNavigationBar ✗ / false
    statusBarColor transparent
    navigationBarColor transparent
    API29

    View full-size slide

  129. <br/><item name="android:windowLightStatusBar" tools:targetApi="m">@bool/<br/>use_light_status</item><br/><item name="android:windowLightNavigationBar"<br/>tools:targetApi=“o_mr1">@bool/use_light_navigation</item><br/>

    View full-size slide

  130. <br/><item name="android:windowLightStatusBar" tools:targetApi="m">@bool/<br/>use_light_status</item><br/><item name="android:windowLightNavigationBar"<br/>tools:targetApi=“o_mr1">@bool/use_light_navigation</item><br/>

    View full-size slide

  131. <br/><item name="android:windowLightStatusBar" tools:targetApi="m">@bool/<br/>use_light_status</item><br/><item name="android:windowLightNavigationBar"<br/>tools:targetApi=“o_mr1">@bool/use_light_navigation</item><br/>

    true

    values-notnight-v23

    View full-size slide

  132. <br/><item name="android:windowLightStatusBar" tools:targetApi="m">@bool/<br/>use_light_status</item><br/><item name="android:windowLightNavigationBar"<br/>tools:targetApi=“o_mr1">@bool/use_light_navigation</item><br/>

    true

    values-notnight-v23

    false

    values

    View full-size slide

  133. <br/><item name="android:windowLightStatusBar" tools:targetApi="m">@bool/<br/>use_light_status</item><br/><item name="android:windowLightNavigationBar"<br/>tools:targetApi=“o_mr1">@bool/use_light_navigation</item><br/>

    View full-size slide

  134. @bool/
    use_light_status
    tools:targetApi=“o_mr1">@bool/use_light_navigation


    true

    values-notnight-v27

    View full-size slide


  135. true

    values-notnight-v27

    false

    values
    @bool/
    use_light_status
    tools:targetApi=“o_mr1">@bool/use_light_navigation

    View full-size slide

  136. <br/><item name="android:statusBarColor">@color/status_bar_scrim</item><br/><item name="android:navigationBarColor">@color/nav_bar_scrim</item><br/>

    View full-size slide

  137. <br/><item name="android:statusBarColor">@color/status_bar_scrim</item><br/><item name="android:navigationBarColor">@color/nav_bar_scrim</item><br/>

    View full-size slide

  138. <br/><item name="android:statusBarColor">@color/status_bar_scrim</item><br/><item name="android:navigationBarColor">@color/nav_bar_scrim</item><br/>
    values-night
    @color/transparent

    View full-size slide

  139. <br/><item name="android:statusBarColor">@color/status_bar_scrim</item><br/><item name="android:navigationBarColor">@color/nav_bar_scrim</item><br/>
    values-night
    @color/transparent
    values-notnight-v23
    @color/transparent

    View full-size slide

  140. <br/><item name="android:statusBarColor">@color/status_bar_scrim</item><br/><item name="android:navigationBarColor">@color/nav_bar_scrim</item><br/>
    values-night
    @color/transparent
    values-notnight-v23
    @color/transparent
    values
    @color/system_ui_scrim_black

    View full-size slide

  141. values-v29
    <br/><item name="android:statusBarColor">@color/transparent</item><br/><item name="android:navigationBarColor">@color/transparent</item><br/>

    View full-size slide

  142. ςʔϚͰߏங͢Δ·ͱΊ
    • σΟϨΫτϦΛ૊Έ߹Θͤͯɺߏங͢Δ

    • values

    • values-night

    • values-notnight-v23

    • values-notnight-v27

    • values-v29

    • API29͸transparentΛࢦఆ͢Δͱɺ͍͍ײ͡ʹഎܠ৭Λௐ੔ͯ͘͠Ε
    Δ

    View full-size slide

  143. StatusBarScrim

    View full-size slide

  144. android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorSurface"
    android:elevation="@dimen/appbar_elevation"
    android:outlineProvider="none"
    android:fitsSystemWindows="true" />

    View full-size slide

  145. class StatusBarScrim
    override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
    if (insets != lastWindowInsets) {
    lastWindowInsets = insets
    // Set this view as invisible if there isn't a top inset
    visibility = if (insets.systemWindowInsetTop > 0) VISIBLE else INVISIBLE
    // Request a layout to change size
    requestLayout()
    }
    return insets
    }

    View full-size slide

  146. class StatusBarScrim
    override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
    if (insets != lastWindowInsets) {
    lastWindowInsets = insets
    // Set this view as invisible if there isn't a top inset
    visibility = if (insets.systemWindowInsetTop > 0) VISIBLE else INVISIBLE
    // Request a layout to change size
    requestLayout()
    }
    return insets
    }

    View full-size slide

  147. class StatusBarScrim
    override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
    if (insets != lastWindowInsets) {
    lastWindowInsets = insets
    // Set this view as invisible if there isn't a top inset
    visibility = if (insets.systemWindowInsetTop > 0) VISIBLE else INVISIBLE
    // Request a layout to change size
    requestLayout()
    }
    return insets
    }

    View full-size slide

  148. android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorSurface"
    android:elevation="@dimen/appbar_elevation"
    android:outlineProvider="none"
    android:fitsSystemWindows="true" />

    View full-size slide

  149. InsetterϥΠϒϥϦ

    View full-size slide

  150. InsetterϥΠϒϥϦ
    • WindowInsetsɺSystemUiVisibilityΛ؆୯ʹѻ͏͜ͱ͕ग़དྷΔ
    void setEdgeToEdgeSystemUiFlags(View view, boolean enabled)

    View full-size slide

  151. InsetterϥΠϒϥϦ
    • WindowInsetsɺSystemUiVisibilityΛ؆୯ʹѻ͏͜ͱ͕ग़དྷΔ
    void setOnApplyInsetsListener(View view, OnApplyInsetsListener listener)
    public interface OnApplyInsetsListener {
    void onApplyInsets(
    View view, WindowInsetsCompat insets, ViewState initialState
    );
    }

    View full-size slide

  152. • WindowInsetsɺSystemUiVisibilityΛ؆୯ʹѻ͏͜ͱ͕ग़དྷΔ
    void setOnApplyInsetsListener(View view, OnApplyInsetsListener listener)
    public interface OnApplyInsetsListener {
    void onApplyInsets(
    View view, WindowInsetsCompat insets, ViewState initialState
    );
    }
    view.updatePadding(
    top = view.paddingTop + insets.systemWindowInsetTop
    )

    View full-size slide

  153. • WindowInsetsɺSystemUiVisibilityΛ؆୯ʹѻ͏͜ͱ͕ग़དྷΔ
    void setOnApplyInsetsListener(View view, OnApplyInsetsListener listener)
    public interface OnApplyInsetsListener {
    void onApplyInsets(
    View view, WindowInsetsCompat insets, ViewState initialState
    );
    }
    view.updatePadding(
    top = view.paddingTop + insets.systemWindowInsetTop
    )
    setOnApplyWindowInsetsListener͸ෳ਺ճίʔϧ͞ΕΔͷͰɺ͜Εͩͱμϝ

    View full-size slide

  154. • WindowInsetsɺSystemUiVisibilityΛ؆୯ʹѻ͏͜ͱ͕ग़དྷΔ
    void setOnApplyInsetsListener(View view, OnApplyInsetsListener listener)
    public interface OnApplyInsetsListener {
    void onApplyInsets(
    View view, WindowInsetsCompat insets, ViewState initialState
    );
    }
    view.updatePadding(
    top = initialState.paddings.top + insets.systemWindowInsetTop
    )

    View full-size slide

  155. ࢀߟ
    • https://github.com/google/iosched

    • https://medium.com/androiddevelopers/gesture-navigation-
    going-edge-to-edge-812f62e4e83e

    • https://medium.com/androiddevelopers/why-would-i-want-
    to-fitssystemwindows-4e26d9ce1eec

    • https://speakerdeck.com/ytakahashi/xiang-jie-windowinsets

    • https://satoshun.github.io/2020/01/android-
    fitssystemwindows/

    View full-size slide

  156. DroidKaigi
    2020
    System UIΛίϯτϩʔϧͯ͠ɺ
    ը໘Λ࠷େݶʹੜ͔ͨ͠ΞϓϦΛߏங͢Δ
    Sato Shun

    GitHub: satoshun

    Twitter: @stsn_jp

    CyberAgent, Inc.

    View full-size slide