What's new in Android Q UI

What's new in Android Q UI

Check out the new UI features in Android Q. The presentation covers: Foldables, Bubbles, the new ShareSheet, Gesture Navigation, Dark Theme and Settings Panels.

B5b545babbc646ce40053512edbcf5b0?s=128

Manuel Vivo

July 03, 2019
Tweet

Transcript

  1. Murat Yener @yenerm What’s new in Android UI @manuelvicnt Manuel

    Vivo
  2. Murat Yener @yenerm What’s new in Android UI @manuelvicnt Manuel

    Vivo
  3. Foldables Bubbles Share Sheet Gestural Navigation & Edge to Edge

    Dark Theme Settings Panels What the
  4. Foldables

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

  6. Tablets Chrome OS

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

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

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

  10. New aspect ratios maxAspectRatio available now

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

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

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

  14. 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
  15. Tablets Multi-resume Compat mode Configuration Changes

  16. 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
  17. Bubbles

  18. Bubbles

  19. Bubbles !Over the notification shade !On the lock screen !Over

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

    !Autogranted (since M)
  21. 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
  22. Bubbles Users can opt-out!

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

    never appear as bubbles !Allow - all notifications sent with BubbleMetaData will appear as bubbles
  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. <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" />

  27. // 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)
  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 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()
  34. // 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)
  35. //Create an expanded bubble val bubbleMetadata = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIntent(bubbleIntent)

    .setAutoExpandBubble(true) .setSuppressInitialNotification(true) .build()
  36. 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.
  37. Resources - Feature documentation https://developer.android.com/preview/features/bubbles - Sample code https://github.com/googlesamples/android-Bubbles

  38. ShareSheet

  39. New Share Sheet New content preview Better Direct Share

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

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

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

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

    library Up to 8 share targets
  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. 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>
  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. 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))
  52. AndroidManifest.xml // Add dependency -> androidx.sharetarget:sharetarget:1.0.0-alpha02 // 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. AndroidManifest.xml // Add dependency -> androidx.sharetarget:sharetarget:1.0.0-alpha02 // 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" />
  54. Custom Targets Encourage sharing within your app Your custom Intents

    via EXTRA_INITIAL_INTENTS Your custom ChooserTargets via EXTRA_CHOOSER_TARGETS
  55. 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
  56. & Edge to Edge Display Gestural Navigation

  57. None
  58. 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?
  59. How? !Make your app edge to edge 10:00

  60. !Make your app edge to edge !Use insets to modify

    your UI !Use gesture exclusion zones for controls on the edges How? 10:00
  61. Edge to Edge

  62. Edge to Edge 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
  63. Edge to Edge 1. Change system bar colors

  64. <!-- 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>
  65. Edge to Edge 1. Change system bar colors 2. Request

    to be laid out fullscreen
  66. //onCreate view.systemUiVisibility = //lay out as if nav bar is

    hidden View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or //lay out for worst case the app can expect //as a continuous state View.SYSTEM_UI_FLAG_LAYOUT_STABLE or //lay out behind the status bar (optional) View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
  67. 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() dragables
  68. Insets Horizontal controls on right and left edges Vertical controls

    on bottom edge ^ ^ X
  69. // 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 } ^ ^
  70. 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
  71. val loc = IntArray(2) slider.getLocationOnScreen(loc) val rect1 = Rect(0, loc[1],

    /*exclusion width*/, loc[1] + slider.height) val rect2 = Rect(loc[0] + slider.width - /*exclusion width*/, loc[1], loc[0] + slider.width, loc[1] + slider.height) //Call on onDraw or onLayout setSystemGestureExclusionRects(view, listOf(rect1, rect2))
  72. val loc = IntArray(2) slider.getLocationOnScreen(loc) val rect1 = Rect(0, loc[1],

    /*exclusion width*/, loc[1] + slider.height) val rect2 = Rect(loc[0] + slider.width - /*exclusion width*/, loc[1], loc[0] + slider.width, loc[1] + slider.height) //Call on onDraw or onLayout setSystemGestureExclusionRects(view, listOf(rect1, rect2))
  73. val loc = IntArray(2) slider.getLocationOnScreen(loc) val rect1 = Rect(0, loc[1],

    /*exclusion width*/, loc[1] + slider.height) val rect2 = Rect(loc[0] + slider.width - /*exclusion width*/, loc[1], loc[0] + slider.width, loc[1] + slider.height) //Call on onDraw or onLayout setSystemGestureExclusionRects(view, listOf(rect1, rect2))
  74. Don’t create a exclusion zone for whole side • Horizontal

    carousels that span the entire width of the screen • ViewPagers • Drawers (1.1.0 alpha2)
  75. Resources - Documentation https://developer.android.com/preview/features/gesturalnav - Gestural Navigation DevByte https://youtu.be/Ljtz7T8R_Hk -

    Edge-to-Edge DevByte https://youtu.be/Nf-fP2u9vjI - WindowInsets https://chris.banes.dev/2019/04/12/insets-listeners-to-layouts/
  76. Dark Theme

  77. Dark Theme

  78. System Settings Applies to ALL apps

  79. Users will expect your app to support Dark Theme

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

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

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

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

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

  85. 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“ />
  86. Use Theme attributes your_view.xml <ConstraintLayout ... background= "#ffffff" /> <TextView

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

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

    Int) Note: Values are not persisted
  89. Reply

  90. Automatically convert your app to Dark Theme Force Dark

  91. Automatically convert your app to Dark Theme Force Dark

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

  93. MyActivity.kt myView.setForceDarkAllowed(false)

  94. Force Dark Custom Theme

  95. 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
  96. Settings Panels

  97. Settings Panels

  98. Go to settings to…

  99. Ask to leave the app?

  100. Settings.Panel.* •ACTION_INTERNET_CONNECTIVITY •ACTION_WIFI •ACTION_NFC •ACTION_VOLUME

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

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

  103. Thanks! Q uestions? Murat Yener @yenerm @manuelvicnt Manuel Vivo