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

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.

Manuel Vivo

July 03, 2019
Tweet

More Decks by Manuel Vivo

Other Decks in Programming

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-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" />
  29. 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" />
  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. !Make your app edge to edge !Use insets to modify

    your UI !Use gesture exclusion zones for controls on the edges How? 10:00
  34. 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
  35. //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
  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() dragables
  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. 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))
  40. 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))
  41. 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))
  42. 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)
  43. 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/
  44. Two ways to implement it 1. Custom theme (recommended) AppCompat

    and MaterialComponents library 2. Force Dark