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

ANDROID AT LARGE: HOW TO BRING OPTIMISED EXPERIENCES TO THE BIG SCREEN

ANDROID AT LARGE: HOW TO BRING OPTIMISED EXPERIENCES TO THE BIG SCREEN

With Android making its way to new form factors, and OEMs pushing the boundaries with new technology, it’s time to take a look at what these environments bring to you and your users. It’s crucial that your developer workflow accounts for resizability, new display sizes and aspect ratios, multi display, and app continuity to provide your users the best experience no matter where they are. The days of taking the easy road with screenOrientation="portrait" are quickly coming to an end.

In this talk we discuss the challenges faced when looking to support Android on various platforms, and how to make sure that you’re providing a great experience on all of these form-factors. You will leave this talk with an actionable checklist that helps stabilise your app and user experience on any device.

Pietro F. Maggi

November 27, 2020
Tweet

More Decks by Pietro F. Maggi

Other Decks in Technology

Transcript

  1. Android at Large Optimized Experiences for the Big Screen Pietro

    F. Maggi @pfmaggi Kenneth Ford @KennethFSWE
  2. Different contexts of use Quick actions vs longer sessions Gaming,

    Media, Social, Productivity Immersive Quick actions Multi-tasking Communication, Music Productivity, Communication
  3. New Aspect Ratios Test & support non-standard aspect ratios If

    necessary, use new minAspectRatio or existing maxAspectRatio attributes to restrict the ratios your app supports.
  4. Layouts You have more space, use it! Think about how

    you can surface more information or make your users more efficient Larger screens provide the opportunity for more immersive experiences No one size fits all solution Check out our Material Studies for inspiration! material.io/design/material-studies/
  5. KeyboardFocus.kt // Standard view myView.setFocusable(true) // Override default arrow mapping

    myView.setNextFocusLeftId(R.id.view_to_left) myView.setNextFocusRightId(R.id.view_to_right) myView.setNextFocusTopId(R.id.view_to_top) myView.setNextFocusBottomId(R.id.view_to_bottom) // Override default tab mapping myView.setNextFocusForwardId(R.id.next_view)
  6. EnterKey.kt // Listen for release of enter key myView.setOnKeyListener {

    v, keyCode, event -> if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) { // Insert behavior here true } else { false } }
  7. EnterKey.kt // Listen for release of enter key myView.setOnKeyListener {

    v, keyCode, event -> if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) { // Insert behavior here true } else { false } }
  8. EnterKey.kt // Listen for release of enter key myView.setOnKeyListener {

    v, keyCode, event -> if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) { // Insert behavior here true } else { false } }
  9. Ctrl_Z.kt override fun dispatchKeyShortcutEvent(event: KeyEvent?): Boolean { if (event?.keyCode ==

    KeyEvent.KEYCODE_Z && event.hasModifiers(KeyEvent.META_CTRL_ON)) { // Undo action return true } return super.dispatchKeyShortcutEvent(event) }
  10. Ctrl_Shift_Z.kt override fun dispatchKeyShortcutEvent(event: KeyEvent?): Boolean { if (event?.keyCode ==

    KeyEvent.KEYCODE_Z && event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { // Redo action return true } return super.dispatchKeyShortcutEvent(event) }
  11. Pointers Long press action == right click actions onContextClickListener Change

    colors/elevation when hovering onHoverListener Update mouse pointers on hover View.pointerIcon
  12. yourView.setOnHoverListener{ view, motionEvent -> when (motionEvent.actionMasked) { MotionEvent.ACTION_HOVER_ENTER -> {

    // UI Change } MotionEvent.ACTION_HOVER_EXIT -> { // Revert } } } Highlight Elements on Hover
  13. App Continuity Seamlessly continue what you’re doing! The ability to

    fold and unfold devices brings more spotlight to configuration changes and how your app handles them. Your app should restore the same state and location the user was in before folding or unfolding.
  14. App Continuity Beyond foldables, the ability to save and restore

    state through configuration changes is a priority on all platforms. Correctly handling rotation or free-form resizing of windows not only requires the ability to save and restore state, but to be able to re-draw and re-layout with minimal jank. Beyond foldables, the ability to save and restore state through configuration changes is a priority on all platforms.
  15. android:resizeableActivity Indicates whether an activity supports multi-window and multi-display environment.

    However, setting this to false does not mean the activity never needs to resize because of orientation changes, free-form resizing or display folding.
  16. Activity on external screen when folded Compat mode Activity running

    in compat mode when unfolded Android 10 has a new compatibility mode for activities that support neither multi-window nor orientation changes.
  17. Handling configuration changes Unfolding triggers a configuration change for smallestScreenSize,

    screenLayout and screenSize. • Use onSaveInstanceState and ViewModel, or • Handle the configuration change without restarting via handleConfigChange=”..” For the best experience, declare resizeableActivity=”true”.
  18. Android 10 Resumed Resumed Resumed Multi-resume In multi-window, all top

    focusable activities in visible stacks are now in the RESUMED state. Activity can still end up in the PAUSED state if: • There is a transparent activity on top • It’s not currently focusable (PiP) In multi-window, all top focusable activities in visible stacks are now in the RESUMED state.
  19. Resources While multiple apps are resumed, some will disconnect from

    available resources. Watch for camera availability callbacks to continue use. resizeableActivity=false does not guarantee camera access. While multiple apps are resumed, some will disconnect from available resources.
  20. protected void onTopResumedActivityChanged(boolean topResumed) { if (topResumed) { // Top

    resumed activity // Can be a signal to re-acquire exclusive resources } else { // No longer the top resumed activity } } New lifecycle callbacks for top-resumed
  21. Activities on secondary screens Keep in mind that the following

    things may occur: • Context update • Window resize • Resource Changes
  22. // Get current display val activityDisplay = activity.windowManager.defaultDisplay // Get

    current window metrics val windowMetrics = DisplayMetrics() activityDisplay.getMetrics(windowMetrics) // … or val windowMetrics = activity.resources.displayMetrics Activity vs Application context Context contains information about display // Get current display val activityDisplay = activity.windowManager.defaultDisplay // Get current window metrics val windowMetrics = DisplayMetrics() activityDisplay.getMetrics(windowMetrics) // … or val windowMetrics = activity.resources.displayMetrics // Show toast on the current display Toast.makeText(activity, text, duration).show()
  23. Configuration changes If activity handles configuration change, it will be

    notified onConfigurationChanged(newConfig: Configuration?) If it doesn’t, it will be relaunched. onCreate and onConfigurationChanged are good points to check what is the current display for an activity. android:configChanges="density|orientation|screenLayout |screenSize|smallestScreenSize |touchscreen"
  24. // Get available displays val dm = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager

    val displays = dm.displays // Check their characteristics Display.flags // To check if it’s a secure display, etc. Display.metrics // To get the size, resolution and density Display.state // To check if it’s ON/OFF Using secondary screens
  25. // Check if launch is allowed activityManager.isActivityStartAllowedOnDisplay(context, displayId, intent) //

    Launch on specific display val options = ActivityOptions.makeBasic() options.launchDisplayId = targetDisplay.displayId startActivity(intent, options.toBundle()) Using secondary screens
  26. android { bundle { language { enableSplit = true //

    true by default. } density { // The app bundle should not support configuration APKs for // display densities. These resources are instead packaged // with each base and dynamic feature APK. enableSplit = false } abi { enableSplit = true // true by default. } } } Android App Bundles and multiple-display
  27. Jetpack Window Manager Provide a single API surface for different

    types of foldable devices coming to the market. Target the entire category, not a single model.
  28. Features @IntDef({ // A fold on the screen without a

    physical gap. TYPE_FOLD, // A physical separation with a hinge that // allows two display panels to fold. TYPE_HINGE, }) public @interface Type{} github.com/android/user-interface-samples/tree/master/WindowManager
  29. Usage dependencies { implementation "androidx.window:window:1.0.0-alpha01" } github.com/android/user-interface-samples/tree/master/WindowManager var windowManager =

    WindowManager( this /* context */, null /* windowBackend */) val displayFeatures = windowManager.windowLayoutInfo.displayFeatures windowManager.registerDeviceStateChangeCallback( mainThreadExecutor /* Executor */, callback /* Consumer<DeviceState> */)
  30. Mocking - WindowBackend class MidScreenFoldBackend : WindowBackend { override fun

    getDeviceState() = { /* */ } override fun getWindowLayoutInfo(context: Context): WindowLayoutInfo { /* */ } override fun registerDeviceStateChangeCallback( executor: Executor, callback: Consumer<DeviceState> ) { /* */ } override fun unregisterDeviceStateChangeCallback(callback: Consumer<DeviceState>) { /* */ } override fun registerLayoutChangeCallback( context: Context, executor: Executor, callback: Consumer<WindowLayoutInfo> ) { /* */ } override fun unregisterLayoutChangeCallback(callback: Consumer<WindowLayoutInfo>) { /* */ } } github.com/android/user-interface-samples/tree/master/WindowManager var windowManager = WindowManager( this /* context */, null /* windowBackend */)
  31. Android Emulator Multi-Touch Multi-core support Quick Boot OpenGL ES 3.0

    Multiple instances Display cutout Battery mode Headless build
  32. Size Resolution Unfolded 8” 2200x2480 Folded 6.6” 1148x2480 Size Resolution

    Unfolded 7.3” 1536x2152 Folded 4.6” 840x1960 7.3” 7.3” and 8” Emulators 8” Android Emulator
  33. ...and More Developing for Android 11 with the Android Emulator

    medium.com/androiddevelopers/developing-for-android-11-with-the-android-emulator-a9486af2d7ef