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

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

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.

Manuel Vivo

September 10, 2019
Tweet

More Decks by Manuel Vivo

Other Decks in Science

Transcript

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

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

    location the user was in before folding or unfolding.
  3. Multi-resume In multi-window, all top focusable activities in visible stacks

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

    from shared resources: - Camera - Mic - etc. Handle resource loss gracefully
  5. 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
  6. 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
  7. Bubbles - Over the notification shade - On the lock

    screen - Over the keyboard - Over PIP - Over each other
  8. 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
  9. Opt-out? Block - notifications are not blocked, but they will

    never appear as bubbles Allow - all notifications sent with BubbleMetaData will appear as bubbles
  10. // 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)
  11. // 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()
  12. // 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()
  13. // 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()
  14. // 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()
  15. // 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()
  16. // 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()
  17. // 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)
  18. 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.
  19. New Direct Share API Old API is deprecated Publish Sharing

    Shortcuts in advance with the ShortcutManager API ShortcutInfo API supports Sharing Shortcuts
  20. 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>
  21. 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>
  22. 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>
  23. 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>
  24. 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))
  25. 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))
  26. 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))
  27. 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))
  28. 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" />
  29. 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" />
  30. Custom Targets Encourage sharing within your app Your custom Intents

    via EXTRA_INITIAL_INTENTS Your custom ChooserTargets via EXTRA_CHOOSER_TARGETS
  31. 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
  32. 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?
  33. 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
  34. 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
  35. // 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
  36. 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
  37. // 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 } ^ ^
  38. 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
  39. List<Rect> exclusionRects; public void onLayout(...) { // Update rect bounds

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

    bounds and the exclusionRects list setSystemGestureExclusionRects(exclusionRects); } CustomView.java
  41. 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
  42. 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
  43. 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
  44. 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/
  45. Two ways to implement it 1. Custom theme (recommended) AppCompat

    and MaterialComponents library 2. Force Dark