Slide 1

Slide 1 text

What’s Q in Android UI @manuelvicnt Manuel Vivo

Slide 2

Slide 2 text

Foldables Bubbles Share Sheet Gestural Navigation & Edge to Edge Dark Theme Settings Panels What the

Slide 3

Slide 3 text

Foldables

Slide 4

Slide 4 text

Foldables 2 categories: - Fold in - Fold out

Slide 5

Slide 5 text

Tablets Chrome OS

Slide 6

Slide 6 text

Follow best practices to support all the different form factors - Handling Configuration Changes - Resizability

Slide 7

Slide 7 text

Configuration Changes Your app should restore the same state and location the user was in before folding or unfolding.

Slide 8

Slide 8 text

Resizability android:resizeableActivity compat mode

Slide 9

Slide 9 text

New aspect ratios maxAspectRatio available now

Slide 10

Slide 10 text

Multi-resume In multi-window, all top focusable activities in visible stacks are now in the RESUMED state
 onTopResumedActivityChanged(boolean) Resumed Resumed Resumed

Slide 11

Slide 11 text

Resources Even if your app is resumed, it might disconnect from shared resources: - Camera - Mic - etc. Handle resource loss gracefully

Slide 12

Slide 12 text

Consider Drag & Drop For text and images

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Tablets Multi-resume Compat mode Configuration Changes

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Bubbles

Slide 17

Slide 17 text

Bubbles

Slide 18

Slide 18 text

Bubbles - Over the notification shade - On the lock screen - Over the keyboard - Over PIP - Over each other

Slide 19

Slide 19 text

SYSTEM_ALERT_WINDOW Read your screen Put UI on screen (anywhere, anytime…) Autogranted (since M)

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Users can opt-out!

Slide 22

Slide 22 text

Opt-out? Block - notifications are not blocked, but they will never appear as bubbles Allow - all notifications sent with BubbleMetaData will appear as bubbles

Slide 23

Slide 23 text

Slide 24

Slide 24 text

Slide 25

Slide 25 text

Slide 26

Slide 26 text

// 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)

Slide 27

Slide 27 text

// 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()

Slide 28

Slide 28 text

// 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()

Slide 29

Slide 29 text

// 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()

Slide 30

Slide 30 text

// 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()

Slide 31

Slide 31 text

// 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()

Slide 32

Slide 32 text

// 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()

Slide 33

Slide 33 text

// 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)

Slide 34

Slide 34 text

//Create an expanded bubble val bubbleMetadata = Notification.BubbleMetadata.Builder() .setDesiredHeight(600) .setIntent(bubbleIntent) .setAutoExpandBubble(true) .setSuppressInitialNotification(true) .build()

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

Resources - Feature documentation https://developer.android.com/preview/features/bubbles - Sample code https://github.com/googlesamples/android-Bubbles

Slide 37

Slide 37 text

ShareSheet

Slide 38

Slide 38 text

New Share Sheet New content preview Better Direct Share

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Direct Share Shows app-specific options in the Sharesheet

Slide 41

Slide 41 text

New Direct Share API Old API is deprecated Publish Sharing Shortcuts in advance with the ShortcutManager API ShortcutInfo API supports Sharing Shortcuts

Slide 42

Slide 42 text

New Direct Share API Backwards compatible using the sharetarget androidX library Up to 8 share targets

Slide 43

Slide 43 text

shortcuts.xml file included in the AndroidManifest.xml file

Slide 44

Slide 44 text

shortcuts.xml file included in the AndroidManifest.xml file

Slide 45

Slide 45 text

shortcuts.xml file included in the AndroidManifest.xml file

Slide 46

Slide 46 text

shortcuts.xml file included in the AndroidManifest.xml file

Slide 47

Slide 47 text

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))

Slide 48

Slide 48 text

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))

Slide 49

Slide 49 text

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))

Slide 50

Slide 50 text

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))

Slide 51

Slide 51 text

AndroidManifest.xml // Add dependency -> androidx.sharetarget:sharetarget:1.0.0-beta01 // This goes to the Activity that will handle the share intent

Slide 52

Slide 52 text

AndroidManifest.xml // Add dependency -> androidx.sharetarget:sharetarget:1.0.0-beta01 // This goes to the Activity that will handle the share intent

Slide 53

Slide 53 text

Custom Targets Encourage sharing within your app Your custom Intents via EXTRA_INITIAL_INTENTS Your custom ChooserTargets via EXTRA_CHOOSER_TARGETS

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

& Edge to Edge Display Gestural Navigation

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

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?

Slide 58

Slide 58 text

How? 1. Make your app edge to edge 10:00

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

Recoloring

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Edge to Edge 1. Change system bar colors

Slide 63

Slide 63 text

<item name=“android:statusBarColor”> @android:color/transparent </item> <item name="android:navigationBarColor"> @android:color/transparent </item>

Slide 64

Slide 64 text

Edge to Edge 1. Change system bar colors 2. Request to be laid out fullscreen

Slide 65

Slide 65 text

// 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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Insets Horizontal controls on right and left edges Vertical controls on bottom edge ^ ^ X

Slide 68

Slide 68 text

// 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 } ^ ^

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

View.setSystemGestureExclusionRects(List rects)

Slide 71

Slide 71 text

List exclusionRects; public void onLayout(...) { // Update rect bounds and the exclusionRects list setSystemGestureExclusionRects(exclusionRects); } CustomView.java

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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/

Slide 77

Slide 77 text

Dark Theme

Slide 78

Slide 78 text

Dark Theme

Slide 79

Slide 79 text

System Settings Applies to ALL apps

Slide 80

Slide 80 text

Users will expect your app to support Dark Theme

Slide 81

Slide 81 text

Two ways to implement it 1. Custom theme (recommended) AppCompat and MaterialComponents library 2. Force Dark

Slide 82

Slide 82 text

Extend a DayNight theme It enables the -night resource qualifier while in Dark Theme. Custom Dark Theme

Slide 83

Slide 83 text

styles.xml ...

Slide 84

Slide 84 text

styles.xml ...

Slide 85

Slide 85 text

res/values/colors.xml #000000 res/values-night/colors.xml #FFFFFF

Slide 86

Slide 86 text

res/values/colors.xml #000000 res/values-night/colors.xml #FFFFFF your_view.xml

Slide 87

Slide 87 text

Use Theme attributes your_view.xml

Slide 88

Slide 88 text

Use Theme attributes your_view.xml

Slide 89

Slide 89 text

DayNight theme Tell the system what theme to use AppCompatDelegate.setDefaultNightMode(mode: Int) Note: Values are not persisted

Slide 90

Slide 90 text

Reply

Slide 91

Slide 91 text

Automatically convert your app to Dark Theme Force Dark

Slide 92

Slide 92 text

Automatically convert your app to Dark Theme Force Dark

Slide 93

Slide 93 text

styles.xml <item name="android:forceDarkAllowed">true</item>

Slide 94

Slide 94 text

MyActivity.kt myView.setForceDarkAllowed(false)

Slide 95

Slide 95 text

Force Dark Custom Theme

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

Settings Panels

Slide 98

Slide 98 text

Settings Panels

Slide 99

Slide 99 text

Go to settings to…

Slide 100

Slide 100 text

Ask to leave the app?

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

Thanks! Questions? @manuelvicnt Manuel Vivo