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

What's new in Adaptive Android development

What's new in Adaptive Android development

2025년 07월 26일 (토) Google I/O Extended Incheon 2025 발표자료입니다.
"What's new in Adaptive Android development"
https://www.ticketa.co/events/7

Avatar for Sungyong An

Sungyong An

July 26, 2025
Tweet

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. Google I/O Extended 25 Adaptive Apps? Link: h tt ps://developer.android.com/develop/ui/compose/build-adaptive-apps

    ׮নೠ ചݶ ௼ӝী ߈਽ೞৈ, ୭੸ചػ ࢎਊ੗ ҃೷ਸ ઁҕೞח জ ৘द) ോ؀ಪীࢲח bottom navigation bar ಴द ؀ഋ ചݶীࢲח navigation rail ಴द
  2. Google I/O Extended 25 • Multi-window mode • Split-screen •

    Picture-in-picture • Desktop windowing Why build adaptive UIs? Link: h tt ps://developer.android.com/develop/ui/compose/layouts/adaptive/suppo rt -multi-window-mode
  3. Google I/O Extended 25 • Multi-window mode • Form Factors

    • Phone, Foldable, Tablet, Desktop, TV • Car, XR Why build adaptive UIs? Link: h tt ps://developer.android.com/develop#devices
  4. Google I/O Extended 25 3x usage 3x usage time for

    tablet and phone users vs phone-only users. Aggregated across 6 of the major streaming apps in the US.
  5. Google I/O Extended 25 • Multi-window mode • Form Factors

    • Google Play • Large screen app quality guidelines • TIER 3: ready • TIER 2: optimized • TIER 1: differentiated Why build adaptive UIs? Link: h tt ps://developer.android.com/docs/quality-guidelines/large-screen-app-quality
  6. Google I/O Extended 25 • Multi-window mode • Form Factors

    • Google Play • Android 16 • Ignore orientation, resizability, and aspect ratio restrictions • Connected displays Why build adaptive UIs? Link: h tt ps://developer.android.com/about/versions/16/behavior-changes-16#adaptive-layouts
  7. Google I/O Extended 25 Responsive design Link: h tt ps://developer.android.com/develop/ui/views/layout/responsive-adaptive-design-with-views

    • Responsive width and height • match_parent • wrap_content • layout_weight • ConstraintLayout View
  8. Google I/O Extended 25 Link: h tt ps://uxplanet.org/adaptive-vs-responsive-web-design-eead0c2c28a8 Responsive design

    vs Adaptive design Responsive design (߈਽ഋ জ) • ࢎਊ оמೠ ҕрী ݏ୾ ٣੗ੋ ਃࣗ੄ ߓ஖ܳ ઑ੿ೠ׮ Adaptive design (੸਽ഋ জ) • ࢎਊ оמೠ ҕрী ݏ୾ ੸੺ೠ ۨ੉ইਓਸ ࢶఖೠ׮
  9. Google I/O Extended 25 Adaptive design Link: h tt ps://developer.android.com/develop/ui/views/layout/responsive-adaptive-design-with-views

    • Alternative layout resources • Smallest width qualifier • ӝӝ੄ അ੤ ߑೱী ҙ҅হ੉ زੌ res/layout-sw600dp/ • Available width qualifier • ӝӝ ߑೱী ٮۄ ߸҃ؼ ࣻ ੓਺ res/layout-w600dp/ w sw View
  10. Google I/O Extended 25 Adaptive design Link: h tt ps://developer.android.com/develop/ui/views/layout/twopane

    • SlidingPaneLayout • View, Fragment • ௾ ചݶীࢲח ݾ۾ ହҗ ࣁࠗ੿ࠁ ହ੉ աۆ൤ ಴द • (ց࠺о ୽࠙ೞ૑ ঋ਷) ੘਷ ചݶীࢲח ف ହ੉ Ҁ୛ࢲ ಴द View
  11. Google I/O Extended 25 Adaptive design • Window Size Classes

    • Width Breakpoints • Compact: w < 600dp • Medium: 600dp ≤ w < 840dp • Expanded: 840dp ≤ w Link: h tt ps://developer.android.com/develop/ui/views/layout/use-window-size-classes View
  12. Google I/O Extended 25 Adaptive design • Window Size Classes

    • Height Breakpoints • Compact: h < 480dp • Medium: 480dp ≤ h < 900dp • Expanded: h ≥ 900dp Link: h tt ps://developer.android.com/develop/ui/views/layout/use-window-size-classes View
  13. private fun computeWindowSizeClasses() { val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this) val width

    = metrics.bounds.width() val height = metrics.bounds.height() val density = resources.displayMetrics.density val windowSizeClass = WindowSizeClass.compute(width/density, height/density) // COMPACT, MEDIUM, or EXPANDED val widthWindowSizeClass = windowSizeClass.windowWidthSizeClass // COMPACT, MEDIUM, or EXPANDED val heightWindowSizeClass = windowSizeClass.windowHeightSizeClass // Use widthWindowSizeClass and heightWindowSizeClass. }
  14. class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) val container: ViewGroup = binding.container container.addView(object : View(this) { override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) computeWindowSizeClasses() } }) computeWindowSizeClasses() } }
  15. Google I/O Extended 25 Adaptive design Link: h tt ps://developer.android.com/develop/ui/views/layout/activity-embedding

    • Activity embedding • Activities • Android 12L+ • Jetpack WindowManager View
  16. SplitInitializer.kt (+ androidx.startup) class SplitInitializer : Initializer<RuleController> { override fun

    create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.split_config)) } } ... }
  17. Google I/O Extended 25 Adaptive design • BoxWithConstraints • minWidth

    • maxWidth • minHeight • maxHeight Compose @Composable fun AdaptivePane() { BoxWithConstraints { if (maxWidth < 400.dp) { OnePane(/* ... */) } else { TwoPane(/* ... */) } } }
  18. Google I/O Extended 25 Adaptive design • Window Size Classes

    • Width/Height Breakpoints Compose Link: h tt ps://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes
  19. @Composable fun AdaptivePane( windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass ) { when

    (windowSizeClass.widthSizeClass) { WindowWidthSizeClass.Compact -> ... WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded -> ... } }
  20. @Composable fun AdaptivePane( windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass ) { when

    (windowSizeClass.widthSizeClass) { // Deprecated WindowWidthSizeClass.Compact -> ... WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded -> ... } } Deprecated
  21. val windowDpSize = with(LocalDensity.current) { LocalWindowInfo.current.containerSize.toSize().toDpSize() } val windowSizeClass =

    WindowSizeClass.BREAKPOINTS_V1.computeWindowSizeClass( windowDpSize.width.value, windowDpSize.height.value)
  22. val windowDpSize = with(LocalDensity.current) { LocalWindowInfo.current.containerSize.toSize().toDpSize() } val windowSizeClass =

    WindowSizeClass.BREAKPOINTS_V1.computeWindowSizeClass( windowDpSize.width.value, windowDpSize.height.value) if (windowSizeClass.isWidthAtLeastBreakpoint( WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND)) { // 840dp => Expanded width } else if (windowSizeClass.isWidthAtLeastBreakpoint( WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND)) { // 600dp => Medium width } else { // Compact width }
  23. Google I/O Extended 25 Adaptive design • Compose Material3 Adaptive

    • Navigation Suite • NavigationSuiteScaffold • NavigationBar • NavigationRail Link: h tt ps://developer.android.com/develop/ui/compose/layouts/adaptive/build-adaptive-navigation
  24. Google I/O Extended 25 Adaptive design • Compose Material3 Adaptive

    • Navigation Suite • NavigationSuiteScaffold • Layout • ListDetailPaneScaffold Link: h tt ps://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail List Detail Detail
  25. ListDetailPaneScaffold( ... listPane = { AnimatedPane { ... } },

    detailPane = { AnimatedPane { ... } }, )
  26. Google I/O Extended 25 Adaptive design • Compose Material3 Adaptive

    • Navigation Suite • NavigationSuiteScaffold • Layout • ListDetailPaneScaffold • SupportingPaneScaffold Link: h tt ps://developer.android.com/develop/ui/compose/layouts/adaptive/build-a-suppo rt ing-pane-layout Main Suppo rt ing
  27. SupportingPaneScaffold( ... mainPane = { AnimatedPane { ... } },

    supportingPane = { AnimatedPane { ... } }, )
  28. NavigationSuiteSca ff old ListDetailPaneSca ff old Suppo rt ingPaneSca ff

    old Adaptive library dependency graph Blog: Jetpack Compose APIs for building adaptive layouts using Material guidance now stable
  29. Use the Window Size Classes Opinionated breakpoints to guide your

    layout decisions. Canonical Layouts Proven, versatile layouts that provide an optimal user experience on different form factors. Adaptive apps Don’t reinvent the wheel! Leverage Jetpack libraries like Compose Material Adaptive.
  30. Google I/O Extended 25 Window Size Classes Link: h tt

    ps://developer.android.com/develop/ui/compose/layouts/adaptive/use-window-size-classes • Width Breakpoints • Large • Extra Large Window 1.5
  31. val windowDpSize = with(LocalDensity.current) { LocalWindowInfo.current.containerSize.toSize().toDpSize() } val windowSizeClass =

    WindowSizeClass.BREAKPOINTS_V1.computeWindowSizeClass( windowDpSize.width.value, windowDpSize.height.value) val panes = if (windowSizeClass.isWidthAtLeastBreakpoint( WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND)) { 2 } // 840dp else { 1 }
  32. val windowDpSize = with(LocalDensity.current) { LocalWindowInfo.current.containerSize.toSize().toDpSize() } val windowSizeClass =

    WindowSizeClass.BREAKPOINTS_V2.computeWindowSizeClass( windowDpSize.width.value, windowDpSize.height.value) val panes = if (windowSizeClass.isWidthAtLeastBreakpoint( WindowSizeClass.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND)) { 3 } // 1600dp else if (windowSizeClass.isWidthAtLeastBreakpoint( WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND)) { 2 } // 840dp else { 1 }
  33. ListDetailPaneScaffold( ... // Drag handle/pane expansion code paneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),

    paneExpansionDragHandle = { state -> val interactionSource = remember { MutableInteractionSource() } VerticalDragHandle( modifier = Modifier.paneExpansionDraggable( state, LocalMinimumInteractiveComponentSize.current, interactionSource ), interactionSource = interactionSource ) } ) Example
  34. Google I/O Extended 25 Compose Material3 Adaptive • Pane adaptation

    strategy • Levitate • floating or docked Link: h tt ps://m3.material.io/foundations/layout/applying-layout/pane-layouts Adaptive 1.2
  35. val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>( // Levitate into dialog window on

    smaller size classes adaptStrategies = ListDetailPaneScaffoldDefaults.adaptStrategies( extraPaneAdaptStrategy = AdaptStrategy.Levitate( strategy = AdaptStrategy.Levitate.Strategy.SinglePaneOnly, alignment = Alignment.Center, scrim = Scrim(), ) ) )
  36. val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>( // Levitate into dialog window on

    smaller size classes adaptStrategies = ListDetailPaneScaffoldDefaults.adaptStrategies( extraPaneAdaptStrategy = AdaptStrategy.Levitate( strategy = AdaptStrategy.Levitate.Strategy.SinglePaneOnly, alignment = Alignment.Center, scrim = Scrim(), ) ) )
  37. val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>( // Levitate into bottom sheet on

    smaller size classes adaptStrategies = ListDetailPaneScaffoldDefaults.adaptStrategies( extraPaneAdaptStrategy = AdaptStrategy.Levitate( strategy = AdaptStrategy.Levitate.Strategy.SinglePaneOnly, alignment = Alignment.BottomCenter, scrim = null, ) ) )
  38. val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>( // Levitate into bottom sheet on

    smaller size classes adaptStrategies = ListDetailPaneScaffoldDefaults.adaptStrategies( extraPaneAdaptStrategy = AdaptStrategy.Levitate( strategy = AdaptStrategy.Levitate.Strategy.SinglePaneOnly, alignment = Alignment.BottomCenter, scrim = null, ) ) ) ListDetailPaneScaffold( extraPane = { AnimatedPane( modifier = Modifier.dragToResize( rememberDragToResizeState(dockedEdge = DockedEdge.Bottom)
  39. Google I/O Extended 25 Compose Material3 Adaptive • Pane adaptation

    strategy! • Levitate • Reflow Link: h tt ps://m3.material.io/foundations/layout/applying-layout/pane-layouts Adaptive 1.2
  40. val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>( // Reflow below Detail pane on

    smaller size classes adaptStrategies = ListDetailPaneScaffoldDefaults.adaptStrategies( extraPaneAdaptStrategy = AdaptStrategy.Reflow( targetPane = ListDetailPaneScaffoldRole.Detail ) ) ) ListDetailPaneScaffold( directive = navigator.scaffoldDirective, value = navigator.scaffoldValue, ...
  41. Google I/O Extended 25 Navigation 3 • Designed to work

    with Compose • full control of the back stack • custom navigation behavior • smaller components Link: goo.gle/nav3 Alpha
  42. Navigation NavHost(...) { composable<Route.ChatsList> { ChatList(...) } composable<Route.ChatThread> { ChatScreen(...)

    } ) Navigation 3 NavDisplay(...) { backStackKey -> when (backStackKey) { is Pane.ChatsList -> NavEntry( key = backStackKey, ) { ChatList(...) } is Pane.ChatThread -> NavEntry( key = backStackKey, ) { ChatScreen(...) } } )
  43. Link: h tt ps://github.com/android/socialite/pull/175 Navigation 3 NavDisplay(...) { backStackKey ->

    when (backStackKey) { is Pane.ChatsList -> NavEntry( key = backStackKey, ) { ChatList(...) } is Pane.ChatThread -> NavEntry( key = backStackKey, ) { ChatScreen(...) } } )
  44. Link: h tt ps://github.com/android/socialite/pull/175 Navigation 3 NavDisplay(...) { backStackKey ->

    when (backStackKey) { is Pane.ChatsList -> NavEntry( key = backStackKey, metadata = ListDetailSceneStrategy.listPane(), ) { ChatList(...) } is Pane.ChatThread -> NavEntry( key = backStackKey, metadata = ListDetailSceneStrategy.detailPane(), ) { ChatScreen(...) } } )
  45. Link: h tt ps://github.com/android/socialite/pull/175 Navigation 3 NavDisplay(...) { backStackKey ->

    when (backStackKey) { is Pane.ChatsList -> NavEntry( key = backStackKey, metadata = ListDetailSceneStrategy.listPane(), ) { ChatList(...) } is Pane.ChatThread -> NavEntry( key = backStackKey, metadata = ListDetailSceneStrategy.detailPane(), ) { ChatScreen(...) } } )
  46. App UI Custom affordance with Intent flags. System UI Window

    controls, taskbar or launcher. New! Natural drag and drop interaction.
  47. modifier = Modifier .dragAndDropSource { _ -> DragAndDropTransferData( clipData =

    multiInstanceClipData(activity), flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG ) }
  48. modifier = Modifier .dragAndDropSource { _ -> DragAndDropTransferData( clipData =

    multiInstanceClipData(activity), flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG ) }
  49. modifier = Modifier .dragAndDropSource { _ -> DragAndDropTransferData( clipData =

    multiInstanceClipData(activity), flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG ) }
  50. fun multiInstanceClipData(activity: Activity): ClipData { val intent = Intent.makeMainActivity(activity.componentName) val

    pendingIntent = PendingIntent.getActivity( activity, 0, intent, PendingIntent.FLAG_IMMUTABLE ) return ClipData( "NewInstance", arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT), ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build() ) }
  51. Column { if (WindowInsets.isCaptionBarVisible) { Row(modifier = Modifier .windowInsetsTopHeight(WindowInsets.captionBar) .fillMaxWidth()

    .background(if (isSystemInDarkTheme()) MaterialTheme.colorScheme.surfaceContainerHigh else MaterialTheme.colorScheme.secondaryContainer) ) { TabRow( selectedTabIndex = selectedTabIndex, modifier = Modifier.windowInsetsPadding(WindowInsets.captionBar.only(Horizontal)) ) { // Draw your tabs } } }
  52. Column { if (WindowInsets.isCaptionBarVisible) { Row(modifier = Modifier .windowInsetsTopHeight(WindowInsets.captionBar) .fillMaxWidth()

    .background(if (isSystemInDarkTheme()) MaterialTheme.colorScheme.surfaceContainerHigh else MaterialTheme.colorScheme.secondaryContainer) ) { TabRow( selectedTabIndex = selectedTabIndex, modifier = Modifier.windowInsetsPadding(WindowInsets.captionBar.only(Horizontal)) ) { // Draw your tabs } } }
  53. Column { if (WindowInsets.isCaptionBarVisible) { Row(modifier = Modifier .windowInsetsTopHeight(WindowInsets.captionBar) .fillMaxWidth()

    .background(if (isSystemInDarkTheme()) MaterialTheme.colorScheme.surfaceContainerHigh else MaterialTheme.colorScheme.secondaryContainer) ) { TabRow( selectedTabIndex = selectedTabIndex, modifier = Modifier.windowInsetsPadding(WindowInsets.captionBar.only(Horizontal)) ) { // Draw your tabs } } }
  54. Column { if (WindowInsets.isCaptionBarVisible) { Row(modifier = Modifier .windowInsetsTopHeight(WindowInsets.captionBar) .fillMaxWidth()

    .background(if (isSystemInDarkTheme()) MaterialTheme.colorScheme.surfaceContainerHigh else MaterialTheme.colorScheme.secondaryContainer) ) { TabRow( selectedTabIndex = selectedTabIndex, modifier = Modifier.windowInsetsPadding(WindowInsets.captionBar.only(Horizontal)) ) { // Draw your tabs } } }
  55. val targetDisplay = ... val intent = Intent(...) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or

    Intent.FLAG_ACTIVITY_CLEAR_TASK) val options = ActivityOptions.makeBasic() options.setLaunchDisplayId(targetDisplay.displayId) startActivity(intent, options.toBundle())
  56. val targetDisplay = ... val intent = Intent(...) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or

    Intent.FLAG_ACTIVITY_CLEAR_TASK) val options = ActivityOptions.makeBasic() options.setLaunchDisplayId(targetDisplay.displayId) startActivity(intent, options.toBundle())
  57. Google I/O Extended 25 Design for XR apps Link: h

    tt ps://developer.android.com/design/ui/xr/guides XR-compatible mobile app XR-compatible large screen app XR-differentiated app
  58. Google I/O Extended 25 XR- differentiated app Spatial panels 3D

    models Spatial environments Link: h tt ps://developer.android.com/design/ui/xr/guides#design-android
  59. Subspace { SpatialPanel( SubspaceModifier .height(824.dp) .width(1400.dp) .movable() .resizable() ) {

    Box(...) { Text("Primary Content") } Orbiter(...) { Text("Orbiter") } } }
  60. Google I/O Extended 25 • Compose Material3 library • NavigationSuiteScaffold

    • BasicAlertDialog • TopAppBar • ListDetailPaneScaffold • SupportingPaneScaffold Material Design for XR Link: h tt ps://developer.android.com/develop/xr/jetpack-xr-sdk/material-design setContent { JetStreamTheme { EnableXrComponentOverrides { App(...) } } }
  61. EnableXrComponentOverrides { } NavigationSuiteScaffold { } isSpatializationEnabled Subspace { SpatialPanel(...)

    { content() } } Surface { NavigationSuiteScaffoldLayout(...) { content() } } false true
  62. Wrapping up Why build adaptive UIs? Resizable app windows, Multiple

    devices per user, Rank higher on Google Play, Android 16 behavior changes Create differentiating experiences. Desktop-like experiences, Support connected displays, Adding XR features Build Once, Run Everywhere! Leverage libraries like: Window Size Classes, Compose Material3 Adaptive, Navigation 3
  63. Google I/O Extended 25 Google I/O ’25 Sessions on adaptive

    apps and form factors: goo.gle/io25-androiddevs-yt • 📱 Adaptive Android development makes your app shine across devices • 🖊 Unlock user productivity with desktop windowing and stylus support • 🕶 Building differentiated apps for Android XR with 3D content • 📺 Engage users on Google TV with excellent TV apps • 🚗 New in-car app experiences Codelabs: Adaptive apps Samples: android/adaptive-apps-samples