Foldable Layout

Important

This article describes functionality and guidance that is in public preview and may be substantially modified before it's generally available. Microsoft makes no warranties, express or implied, with respect to the information provided here.

Important

The content of this view will be available only after the Activity root view is attached to the window. This happens because the implementation is based on Window Manager library and the Window Manager is working only when view is attached to the window.

The FoldableLayout component is a custom layout that can position its children differently when the application runs on foldable devices. The component can split the screen in two areas depending on the position and orientation of the folding feature. When the screen is split, it can hold two child containers either side by side or one above the other.

Single-screen layout Dual-screen layout
app on single screen in wide mode app spanned across two screens in wide mode
app on single screen in tall mode app spanned across two screens in tall mode

XML Attributes

  • single_screen_layout_id - Receives the layout id to be displayed in single screen mode.
  • dual_screen_start_layout_id - Receives the layout id to be displayed in the start container of dual screen mode.
  • dual_screen_end_layout_id - Receives the layout id to be displayed in the start container of dual screen mode.

The FoldableLayout also has the ability to create a single container when the application is running on a foldable device.

We added four new attributes that will help you create the UI you want:

  • dual_portrait_single_layout_id - Receives the layout id for the dual portrait single container.
  • is_dual_portrait_single_container - Creates an empty dual portrait single container.
  • dual_landscape_single_layout_id - Receives the layout id for the dual landscape single container.
  • is_dual_landscape_single_container - Creates an empty dual landscape single container.

smallestScreenSize support

When an Activity transitions to a new screen mode but it is not recreated because flag smallestScreenSize is set in the Manifest file, the layout will detect the new configuration and automatically resize the containers or even add or remove the second container depending on the FoldableLayout configuration.

Android Studio preview

  • tools_hinge_color - Select hinge color in layout preview.
  • tools_screen_mode - Select screen mode in layout preview.

If you add fragments inside xml files to FoldableLayout, you will need the following attributes to preview the fragment UI:

  • show_in_single_screen
  • show_in_dual_screen_start
  • show_in_dual_screen_end
  • show_in_dual_portrait_single_container
  • show_in_dual_landscape_single_container

Container IDs

The containers have the ids:

  • first_container_id
  • second_container_id

No matter the screen orientation or special behaviour set to the layout, if the layout will show only one container, its id will be first_container_id. If it will show two container there will also be second_container_id.

For example:

  • If the application is in single screen mode the container id will be first_container_id.
  • If a transition to dual screen mode in dual portrait is done and we have dual_portrait_single_layout_id or is_dual_portrait_single_container set then there will be only one container in dual screen mode and its id will still be first_container_id.
  • When a transition to dual landscape is done but we don't have the attributes dual_landscape_single_layout_id and is_dual_landscape_single_container set, then both containers with the ids first_container_id and second_container_id will be present.

Create FoldableLayout in code

findViewById<FrameLayout>(R.id.parent).addView(
    FoldableLayout(this, FoldableLayout.Config().apply {
        singleScreenLayoutId = R.layout.single_screen
        dualScreenStartLayoutId = R.layout.dual_screen_start
        dualScreenEndLayoutId = R.layout.dual_screen_end
        dualLandscapeSingleLayoutId = R.layout.single_screen
    })
)

Replace FoldableLayout configuration

The code below will discard the old configuration, replace it with a new one and inflate the view with the new configuration.

findViewById<FoldableLayout>(R.id.surface_duo_layout)
    .newConfigCreator()
    .singleScreenLayoutId(R.layout.single_screen)
    .dualScreenStartLayoutId(R.layout.dual_screen_start)
    .dualScreenEndLayoutId(R.layout.dual_screen_end)
    .reInflate()

Update FoldableLayout configuration

The code below will update the current configuration with the selected attributes and inflate the view:

findViewById<FoldableLayout>(R.id.surface_duo_layout)
    .updateConfigCreator()
    .dualScreenStartLayoutId(R.layout.dual_screen_start)
    .reInflate()

How to use the layout components

To create an application, you can either use an Activity or an Activity with Fragments to handle the UI. The Fragments can also be declared in a layout resource file or can be directly created in an Activity. We'll talk about how the components handle these cases later.

process diagram

Using an Activity

Here, we look at how the components work by using just an Activity to handle the UI.

  1. First, you have to add the FoldableLayout to the *.xml file of the Activity.

    <com.microsoft.device.dualscreen.layouts.FoldableLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/enlightened_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"
        app:single_screen_layout_id="@layout/single_screen_layout"
        app:dual_screen_start_layout_id="@layout/single_screen_layout"
        app:dual_screen_end_layout_id="@layout/dual_screen_end_layout"/>
    
  2. Then, create the three layouts for the different screen modes.

  3. Link your layout to your activity.

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
    }
    

Using Fragments declared in resource files

Here, we see how the components work with fragments that are declared in the *.xml files.

  1. First, you have to add the FoldableLayout to the *.xml file of the Activity.

    <com.microsoft.device.dualscreen.layouts.FoldableLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:single_screen_layout_id="@layout/single_screen_layout"
        app:dual_screen_start_layout_id="@layout/dual_screen_start_layout"
        app:dual_screen_end_layout_id="@layout/dual_screen_end_layout" />
    
  2. Next, you declare your fragments into the singlescreenlayout.xml, dualscreenstartlayout.xml, and dualscreenendlayout.xml files.

    <fragment
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/single_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.microsoft.device.display.samples.contentcontext.MapPointListFragment" />
    
  3. Link your layout to your activity.

    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
        }
    }
    

Using Fragments created in an Activity

Finally, we will see how the components work with fragments that are added using getSupportFragmentManager().beginTransaction().‚Äč

  1. First, you have to add the FoldableLayout to the *.xml file of the Activity.

    <com.microsoft.device.dualscreen.layouts.FoldableLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

    This creates the containers for the views but there will be nothing inflated into them.

  2. You can have access to the containers of the FoldableLayout by using their ids:

    • first_container_id
    • second_container_id
  3. Next please visit fragment manager state handler library as fragments need special care when dealing with screen mode transitions.

View Binding

Important

Please use findViewById as shown in the code snippets below. Do not attempt to use Google View Binding.

Because the FoldableLayout content is inflated when WindowManager has available information about the window layout, you cannot retrieve the child instance and use it. In order to bind the child view, firstly you need to be sure that the FoldableLayout content is ready to be used. For this, you should use the following methods:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    private val contentChangedListener = object : FoldableLayout.ContentChangedListener {
        override fun contentChanged(screenMode: ScreenMode?) {
            // Here, you can retrieve the child instance
            val child = binding.foldableLayout.findViewById<View>(R.id.child_view)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    override fun onResume() {
        super.onResume()
        binding.foldableLayout.addContentChangedListener(contentChangedListener)
    }

    override fun onPause() {
        super.onPause()
        binding.foldableLayout.removeContentChangedListener(contentChangedListener)
    }
}

where:

  • FoldableLayout.ContentChangedListener is the callback that will be called after the FoldableLayout content is inflated.

  • FoldableLayout.addContentChangedListener will register the given FoldableLayout.ContentChangedListener callback.

  • foldableLayout.removeContentChangedListener will unregister the given FoldableLayout.ContentChangedListener callback.

Important

You must add this code inside the onResume and onPause methods from your Activity or Fragment in order to avoid memory leaks.