What's new in Android Q UI - Droidcon Lisbon 2019

B5b545babbc646ce40053512edbcf5b0?s=47 Manuel Vivo
September 10, 2019

What's new in Android Q UI - Droidcon Lisbon 2019

New UI Features are the most visible changes for you app users. As the OS and the other apps move forward, your app can easily left out with a broken experience.

Come to this talk to learn more about new UI features that can make your application stand out. In this talk, you’ll learn how to implement the new Android Q UI features that will bring your app’s User Experience to a new level.

B5b545babbc646ce40053512edbcf5b0?s=128

Manuel Vivo

September 10, 2019
Tweet

Transcript

  1. What’s Q in Android UI @manuelvicnt Manuel Vivo

  2. Foldables Bubbles Share Sheet Gestural Navigation & Edge to Edge

    Dark Theme Settings Panels What the
  3. Foldables

  4. Foldables 2 categories: - Fold in - Fold out

  5. Tablets Chrome OS

  6. Follow best practices to support all the different form factors

    - Handling Configuration Changes - Resizability
  7. Configuration Changes Your app should restore the same state and

    location the user was in before folding or unfolding.
  8. Resizability android:resizeableActivity compat mode

  9. New aspect ratios maxAspectRatio available now

  10. Multi-resume In multi-window, all top focusable activities in visible stacks

    are now in the RESUMED state
 onTopResumedActivityChanged(boolean) Resumed Resumed Resumed
  11. Resources Even if your app is resumed, it might disconnect

    from shared resources: - Camera - Mic - etc. Handle resource loss gracefully
  12. Consider Drag & Drop For text and images

  13. Testing Emulators available in Android Studio 3.5 Test how your

    app reacts to: - Configuration changes - Multi-window and multi-resume - Resizing and new screen ratios
  14. Tablets Multi-resume Compat mode Configuration Changes

  15. Resources - Building apps for Foldables doc https://developer.android.com/preview/features/foldables - Drag

    & drop documentation https://developer.android.com/guide/topics/ui/drag-drop - Build Apps for Foldable, Multi-Display, and Large-Screen Devices IO 2019 video https://www.youtube.com/watch?v=8uQEzv3upy8
  16. Bubbles

  17. Bubbles

  18. Bubbles - Over the notification shade - On the lock

    screen - Over the keyboard - Over PIP - Over each other
  19. SYSTEM_ALERT_WINDOW Read your screen Put UI on screen (anywhere, anytime…)

    Autogranted (since M)
  20. Bubbles - Built into the Notification system - Floats on

    top of other app content - Can be expanded (to reveal app functionality and information) - Can be collapsed/stacked (when not being used) - Looks like a notification on lock screen and always-on-display
  21. Users can opt-out!

  22. Opt-out? Block - notifications are not blocked, but they will

    never appear as bubbles Allow - all notifications sent with BubbleMetaData will appear as bubbles
  23. <activity android:name=".bubbles.BubbleActivity" android:theme="@style/AppTheme.NoActionBar" android:label="@string/title_activity_bubble" android:allowEmbedded="true" android:documentLaunchMode="always" android:resizeableActivity="true" />

  24. <activity android:name=".bubbles.BubbleActivity" android:theme="@style/AppTheme.NoActionBar" android:label="@string/title_activity_bubble" android:allowEmbedded="true" android:documentLaunchMode="always" android:resizeableActivity="true" />

  25. <activity android:name=".bubbles.BubbleActivity" android:theme="@style/AppTheme.NoActionBar" android:label="@string/title_activity_bubble" android:allowEmbedded="true" android:documentLaunchMode="always" android:resizeableActivity="true" />

  26. // Create notification val chatBot = Person.Builder() .setBot(true) .setName("BubbleBot") .setImportant(true)

    .build() val builder = Notification.Builder(context, CHANNEL_ID) .setContentIntent(contentIntent) .setSmallIcon(smallIcon) .setBubbleMetadata(bubbleData) .addPerson(chatBot)
  27. // Create bubble intent val target = Intent(context, BubbleActivity::class.java) val

    bubbleIntent = PendingIntent.getActivity(context, 0, target, 0 /* flags */) // Create bubble metadata val bubbleData = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIcon(Icon.createWithResource(context, R.drawable.icon)) .setIntent(bubbleIntent) .build()
  28. // Create bubble intent val target = Intent(context, BubbleActivity::class.java) val

    bubbleIntent = PendingIntent.getActivity(context, 0, target, 0 /* flags */) // Create bubble metadata val bubbleData = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIcon(Icon.createWithResource(context, R.drawable.icon)) .setIntent(bubbleIntent) .build()
  29. // Create bubble intent val target = Intent(context, BubbleActivity::class.java) val

    bubbleIntent = PendingIntent.getActivity(context, 0, target, 0 /* flags */) // Create bubble metadata val bubbleData = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIcon(Icon.createWithResource(context, R.drawable.icon)) .setIntent(bubbleIntent) .build()
  30. // Create bubble intent val target = Intent(context, BubbleActivity::class.java) val

    bubbleIntent = PendingIntent.getActivity(context, 0, target, 0 /* flags */) // Create bubble metadata val bubbleData = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIcon(Icon.createWithResource(context, R.drawable.icon)) .setIntent(bubbleIntent) .build()
  31. // Create bubble intent val target = Intent(context, BubbleActivity::class.java) val

    bubbleIntent = PendingIntent.getActivity(context, 0, target, 0 /* flags */) // Create bubble metadata val bubbleData = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIcon(Icon.createWithResource(context, R.drawable.icon)) .setIntent(bubbleIntent) .build()
  32. // Create bubble intent val target = Intent(context, BubbleActivity::class.java) val

    bubbleIntent = PendingIntent.getActivity(context, 0, target, 0 /* flags */) // Create bubble metadata val bubbleData = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIcon(Icon.createWithResource(context, R.drawable.icon)) .setIntent(bubbleIntent) .build()
  33. // Create notification val chatBot = Person.Builder() .setBot(true) .setName("BubbleBot") .setImportant(true)

    .build() val builder = Notification.Builder(context, CHANNEL_ID) .setContentIntent(contentIntent) .setSmallIcon(smallIcon) .setBubbleMetadata(bubbleData) .addPerson(chatBot)
  34. //Create an expanded bubble val bubbleMetadata = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIntent(bubbleIntent)

    .setAutoExpandBubble(true) .setSuppressInitialNotification(true) .build()
  35. Best Practices Bubbles take up space and cover other apps.

    Use it only when, ongoing conversations, important notifications Users can opt-out. Make sure your bubble notification works as a normal notification as well. Processes that are launched from a bubble stays in the bubbles (task stack). Keep it simple and task specific.
  36. Resources - Feature documentation https://developer.android.com/preview/features/bubbles - Sample code https://github.com/googlesamples/android-Bubbles

  37. ShareSheet

  38. New Share Sheet New content preview Better Direct Share

  39. Content Preview Intent’s ClipData Intent.EXTRA_TEXT Intent.EXTRA_TITLE

  40. Direct Share Shows app-specific options in the Sharesheet

  41. New Direct Share API Old API is deprecated Publish Sharing

    Shortcuts in advance with the ShortcutManager API ShortcutInfo API supports Sharing Shortcuts
  42. New Direct Share API Backwards compatible using the sharetarget androidX

    library Up to 8 share targets
  43. shortcuts.xml file included in the AndroidManifest.xml file <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" <share-target

    android:targetClass="com.example.android.directshare.SendMessageActivity"> <data android:mimeType="text/plain" /> <category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" /> </share-target> </shortcuts>
  44. shortcuts.xml file included in the AndroidManifest.xml file <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" <share-target

    android:targetClass="com.example.android.directshare.SendMessageActivity"> <data android:mimeType="text/plain" /> <category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" /> </share-target> </shortcuts>
  45. shortcuts.xml file included in the AndroidManifest.xml file <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" <share-target

    android:targetClass="com.example.android.directshare.SendMessageActivity"> <data android:mimeType="text/plain" /> <category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" /> </share-target> </shortcuts>
  46. shortcuts.xml file included in the AndroidManifest.xml file <shortcuts xmlns:android="http://schemas.android.com/apk/res/android" <share-target

    android:targetClass="com.example.android.directshare.SendMessageActivity"> <data android:mimeType="text/plain" /> <category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" /> </share-target> </shortcuts>
  47. PublishSharingShortcuts.kt val categories = setOf( "com.example.android.directshare.category.TEXT_SHARE_TARGET" ) val shortcut =

    ShortcutInfoCompat.Builder(context, "contactID") .setShortLabel(“Jason") .setIcon(IconCompat.createWithResource(context, R.mipmap.ic_jason)) .setIntent(Intent(Intent.ACTION_DEFAULT)) .setCategories(categories) .build() ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcut))
  48. PublishSharingShortcuts.kt val categories = setOf( "com.example.android.directshare.category.TEXT_SHARE_TARGET" ) val shortcut =

    ShortcutInfoCompat.Builder(context, "contactID") .setShortLabel(“Jason") .setIcon(IconCompat.createWithResource(context, R.mipmap.ic_jason)) .setIntent(Intent(Intent.ACTION_DEFAULT)) .setCategories(categories) .build() ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcut))
  49. PublishSharingShortcuts.kt val categories = setOf( "com.example.android.directshare.category.TEXT_SHARE_TARGET" ) val shortcut =

    ShortcutInfoCompat.Builder(context, "contactID") .setShortLabel(“Jason") .setIcon(IconCompat.createWithResource(context, R.mipmap.ic_jason)) .setIntent(Intent(Intent.ACTION_DEFAULT)) .setCategories(categories) .build() ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcut))
  50. PublishSharingShortcuts.kt val categories = setOf( "com.example.android.directshare.category.TEXT_SHARE_TARGET" ) val shortcut =

    ShortcutInfoCompat.Builder(context, "contactID") .setShortLabel(“Jason") .setIcon(IconCompat.createWithResource(context, R.mipmap.ic_jason)) .setIntent(Intent(Intent.ACTION_DEFAULT)) .setCategories(categories) .build() ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcut))
  51. AndroidManifest.xml // Add dependency -> androidx.sharetarget:sharetarget:1.0.0-beta01 // This goes to

    the Activity that will handle the share intent <meta-data android:name="android.service.chooser.chooser_target_service" android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
  52. AndroidManifest.xml // Add dependency -> androidx.sharetarget:sharetarget:1.0.0-beta01 // This goes to

    the Activity that will handle the share intent <meta-data android:name="android.service.chooser.chooser_target_service" android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
  53. Custom Targets Encourage sharing within your app Your custom Intents

    via EXTRA_INITIAL_INTENTS Your custom ChooserTargets via EXTRA_CHOOSER_TARGETS
  54. Resources - Direct Share to an Android app codelab https://codelabs.developers.google.com/codelabs/android-direct-share/index.html

    - Sharing Improvements documentation https://developer.android.com/preview/features/sharing - New ShareSheet in Android Q video https://www.youtube.com/watch?v=qsKVL4FSHVI
  55. & Edge to Edge Display Gestural Navigation

  56. None
  57. Wait why? What’s happening?! 10:00 - Great devices with beautiful

    screens! - Each with own gestures - Hard for developers - Unifying the ecosystem - 3-button navigation?
  58. How? 1. Make your app edge to edge 10:00

  59. 1. Make your app edge to edge 2. Use insets

    to modify your UI 3. Use gesture exclusion zones for controls on the edges How? 10:00
  60. Recoloring

  61. Recoloring From Q and onwards the system is responsible for

    recoloring system buttons and handles Recoloring can be dynamic adaption or static coloring depending on device specs. Older platforms, use translucent nav bar color
  62. Edge to Edge 1. Change system bar colors

  63. <!-- values-29/themes.xml: --> <style name="AppTheme" parent="..."> <item name=“android:statusBarColor”> @android:color/transparent </item>

    <item name="android:navigationBarColor"> @android:color/transparent </item> </style>
  64. Edge to Edge 1. Change system bar colors 2. Request

    to be laid out fullscreen
  65. // onCreate view.systemUiVisibility = // lay out as if nav

    bar is hidden View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or // lay out as the most extreme scenario of any other flags View.SYSTEM_UI_FLAG_LAYOUT_STABLE or // lay out behind the status bar (optional) View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  66. Edge to Edge 1. Change system bar colors 2. Request

    to be laid out fullscreen 3. Use insets to avoid overlaps WindowInsets.getWindowSystemInsets() clickable WindowInsets.getSystemGestureInsets() dragable
  67. Insets Horizontal controls on right and left edges Vertical controls

    on bottom edge ^ ^ X
  68. // get the current peek height val curHeight = behavior.peekHeight

    ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> val gestureInsets = insets.getSystemGestureInsets() //increase original height with inset behavior.peekHeight = gestureInsets.bottom + curHeight insets } ^ ^
  69. Gesture Exclusion Rects - Drag handles that start on the

    edges of the screen - Sliders or seek bars - Pull-out sheets on the left/right edges
  70. View.setSystemGestureExclusionRects(List<Rect> rects)

  71. List<Rect> exclusionRects; public void onLayout(...) { // Update rect bounds

    and the exclusionRects list setSystemGestureExclusionRects(exclusionRects); } CustomView.java
  72. List<Rect> exclusionRects; public void onDraw(Canvas canvas) { // Update rect

    bounds and the exclusionRects list setSystemGestureExclusionRects(exclusionRects); } CustomView.java
  73. Restrictions on overriding gestures - Apps can only opt out

    200 dp of each edge - If apps opt out more, only the bottom 200 dp will be overridden for the app
  74. Restrictions on overriding gestures - Apps can only opt out

    200 dp of each edge - If apps opt out more, only the bottom 200 dp will be overridden for the app
  75. Restrictions on overriding gestures - Apps can only opt out

    200 dp of each edge - If apps opt out more, only the bottom 200 dp will be overridden for the app
  76. Resources - Documentation https://developer.android.com/preview/features/gesturalnav - Gesture Navigation - Going edge

    to edge Droidcon Berlin 2019 talk - WindowInsets https://chris.banes.dev/2019/04/12/insets-listeners-to-layouts/
  77. Dark Theme

  78. Dark Theme

  79. System Settings Applies to ALL apps

  80. Users will expect your app to support Dark Theme

  81. Two ways to implement it 1. Custom theme (recommended) AppCompat

    and MaterialComponents library 2. Force Dark
  82. Extend a DayNight theme It enables the -night resource qualifier

    while in Dark Theme. Custom Dark Theme
  83. styles.xml <style name=“MyTheme" parent=“@style/Theme.AppCompat.DayNight”> ... </style>

  84. styles.xml <style name=“MyTheme” parent=“@style/Theme.MaterialComponents.DayNight”> ... </style>

  85. res/values/colors.xml <color name=“text_primary”>#000000</color> res/values-night/colors.xml <color name=“text_primary">#FFFFFF</color>

  86. res/values/colors.xml <color name=“text_primary”>#000000</color> res/values-night/colors.xml <color name=“text_primary”>#FFFFFF</color> your_view.xml <TextView ... textColor=

    “@color/text_primary“ />
  87. Use Theme attributes your_view.xml <ConstraintLayout ... background= "#ffffff" /> <TextView

    ... textColor= "#000000" />
  88. Use Theme attributes your_view.xml <ConstraintLayout ... background= "?attr/colorSurface" /> <TextView

    ... textColor= "?android:attr/textColorPrimary" />
  89. DayNight theme Tell the system what theme to use AppCompatDelegate.setDefaultNightMode(mode:

    Int) Note: Values are not persisted
  90. Reply

  91. Automatically convert your app to Dark Theme Force Dark

  92. Automatically convert your app to Dark Theme Force Dark

  93. styles.xml <style name="MyTheme" parent=“@style/Theme.AppCompat.DayNight”> <item name="android:forceDarkAllowed">true</item> </style>

  94. MyActivity.kt myView.setForceDarkAllowed(false)

  95. Force Dark Custom Theme

  96. Resources - Code sample https://github.com/googlesamples/android-DarkTheme - Documentation https://developer.android.com/preview/features/darktheme - Dark

    Theme & Gesture Navigation IO 2019 Talk https://www.youtube.com/watch?v=OCHEjeLC_UY
  97. Settings Panels

  98. Settings Panels

  99. Go to settings to…

  100. Ask to leave the app?

  101. Settings.Panel.* - ACTION_INTERNET_CONNECTIVITY - ACTION_WIFI - ACTION_NFC - ACTION_VOLUME

  102. Code val panelIntent = Intent(Settings.Panel.ACTION_WIFI) startActivityForResult(panelIntent)

  103. Resources - Feature overview https://developer.android.com/preview/features#settings-panels - API documentation https://developer.android.com/reference/kotlin/android/provider/ Settings.Panel.html

  104. Thanks! Questions? @manuelvicnt Manuel Vivo