diff --git a/docs/adaptive/CanonicalLayouts.md b/docs/adaptive/CanonicalLayouts.md index e73ec85addf..ec3e6106eb6 100644 --- a/docs/adaptive/CanonicalLayouts.md +++ b/docs/adaptive/CanonicalLayouts.md @@ -8,8 +8,15 @@ path: /adaptive/canonicallayouts/ # Canonical Layouts -**Note:** This doc is in progress and will be updated with more information -soon. +**Contents** + +* [Libraries and APIs](#libraries-and-apis) +* [Demos](#demos) +* [General implementation](#general-implementation) +* [List View demo](#list-view-demo) +* [Feed demo](#feed-demo) +* [Single View Hero demo](#single-view-hero-demo) +* [Supporting Panel demo](#supporting-panel-demo) The canonical layout demos found in the [MDC catalog](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/) @@ -17,9 +24,44 @@ are examples of adaptive layouts where components and views change depending on device configuration, such as screen size, orientation, and/or presence of a physical fold. +This doc discusses the general logic that can be applied to implementations like +the one in the [general implementation section](#general-implementation), while +each demo section discusses its specific implementation. + +Tip: Follow along with the source code to better understand the concepts in this +documentation. + +## Libraries and APIs + +To use the Material library, you will need to add a dependency to the Material +Components for Android library. For more information, see the +[Getting started](https://github.com/material-components/material-components-android/tree/master/docs/getting-started.md) +page. + +The AndroidX +[ConstraintLayout](https://developer.android.com/jetpack/androidx/releases/constraintlayout) +and +[WindowManager](https://developer.android.com/jetpack/androidx/releases/window) +libraries are used to achieve layout adaptivity. For more information about +them, see the following: + +* [Get started with large screens](https://developer.android.com/guide/topics/ui/responsive-layout-overview) +* [Build a Responsive UI with ConstraintLayout](https://developer.android.com/training/constraint-layout) +* [Designing for foldables](https://developer.android.com/training/constraint-layout/foldables) + +For more information about the navigation components used in these demos, check +out their documentation: + +* [BottomNavigation](../components/BottomNavigation.md) +* [NavigationRail](../components/NavigationRail.md) +* [NavigationDrawer](../components/NavigationDrawer.md) +* [FloatingActionButton](../components/FloatingActionButton.md) + +## Demos + The catalog's [Adaptive demo](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/) -has the following canonical layout implementations: +has implementations of the following canonical layouts: * List View demo * [`AdaptiveListViewDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveListViewDemoActivity.java) @@ -31,21 +73,442 @@ has the following canonical layout implementations: * Single View Hero demo * [`AdaptiveHeroDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveHeroDemoActivity.java) * [`AdaptiveHeroDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveHeroDemoFragment.java) +* Supporting Panel demo + * [`AdaptiveSupportingPanelDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveSupportingPanelDemoActivity.java) + * [`AdaptiveSupportingPanelDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveSupportingPanelDemoFragment.java) -## Libraries and APIs +## General implementation -To use the Material library, you will need to add a dependency to the Material -Components for Android library. For more information, go to the -[Getting started](https://github.com/material-components/material-components-android/tree/master/docs/getting-started.md) -page. +Each demo has a main `Activity` class that takes care of displaying the intended +navigation component according to screen size, displays a main `Fragment`, and +communicates with that `Fragment` class. -The AndroidX -[ConstraintLayout](https://developer.android.com/jetpack/androidx/releases/constraintlayout) -and -[WindowManager](https://developer.android.com/jetpack/androidx/releases/window) -libraries are used to achieve the flexibility of the layouts. For more -information about them, it is suggested that you read the following: +We use `ConstraintLayout` and `ConstraintSet` to allow the layouts to adapt to +multiple screen and device configurations, and the `WindowManager` library to +capture specific foldable states. -* [Get started with large screens](https://developer.android.com/guide/topics/ui/responsive-layout-overview) -* [Build a Responsive UI with ConstraintLayout](https://developer.android.com/training/constraint-layout) -* [Designing for foldables](https://developer.android.com/training/constraint-layout/foldables) +### Navigation components + +The demos all display different navigation components according to the screen +size: small screens have a bottom navigation, medium screens have a navigation +rail, and large screens have a standard navigation drawer. Medium screens also +display a modal navigation drawer if the navigation header's button is clicked. + +On the Activity XML of each demo, we add all of the navigation components +mentioned: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +We arrange the views so it defaults to a mobile layout (note how most of the +components have `android:visibility="gone"`). We also set each +`app:layout_constraint*` so that the views will look correct no matter which +navigation component is currently visible. + +In code, in the `Activity` class, we adjust each component visibility by +checking the current screen width. We also set a click listener on the +navigation rail's header button so that it triggers a modal navigation drawer to +be shown. Take a look at +[`AdaptiveUtils.java`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveUtils.java) +to see in detail how that is done. + +### Displaying the main `Fragment` + +On the XML code snippet above, we have a `FrameLayout` that we use to load our +demo's `Fragment`. + +In the `Activity` class: + +```java +@Override +protected void onCreate(@Nullable Bundle bundle) { + ... + + demoFragment = new DemoFragment(); + + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.fragment_container, demoFragment) + .commit(); +} +``` + +### Monitoring foldable device states + +The Activity monitors the demo's foldable device state with the `WindowManager` +library, similar to this +[AndroidX FoldableExperiments example](https://github.com/androidx/constraintlayout/blob/main/projects/FoldableExperiments/app/src/main/java/com/example/experiments/MainActivity.kt). + +The Activity has a `StateContainer` inner class that implements +`Consumer`, where it checks for specific foldable +configurations: + +```java +private class StateContainer implements Consumer { + + public StateContainer() {} + + @Override + public void accept(WindowLayoutInfo windowLayoutInfo) { + + List displayFeatures = windowLayoutInfo.getDisplayFeatures(); + + for (DisplayFeature displayFeature : displayFeatures) { + if (displayFeature instanceof FoldingFeature) { + FoldingFeature foldingFeature = (FoldingFeature) displayFeature; + + // Check for specific FoldingFeatures here and communicate with the + // demo fragment as needed. + ... + + // Here's also where we can find the fold position by using the + // AdaptiveUtils utility class: + Orientation foldOrientation = foldingFeature.getOrientation(); + int foldPosition = + AdaptiveUtils.getFoldPosition(container, foldingFeature, foldOrientation)); + + ... + } + } + ... +} +``` + +Finding the position of the fold is useful for changing the position of a +guideline in order to rearrange views. In the demos, we make use of the +[`ReactiveGuide`](https://github.com/androidx/constraintlayout/blob/main/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ReactiveGuide.java) +to do so. Take a look at +[`AdaptiveUtils.java`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveUtils.java) +to see in detail how the `getFoldPosition` method is implemented. + +### Using `ConstraintSet` + +An easy way to rearrange views in a `ConstraintLayout` is by creating +`ConstraintSet`s. You can create a constraint set for a specific device +configuration in the Fragment class and then update the layout as needed. + +For example, the Supporting Panel demo's Fragment has the +`updatePortraitLayout`, `updateLandscapeLayout`, and `updateTableTopLayout` +methods that the Activity calls when it detects that the device is in portrait, +landscape, or table top mode, respectively. + +## List View demo + +The following shows screenshots of the List View demo in different devices and +screen sizes. + +Portrait: + +!["List View demo in portrait."](assets/canonical_layouts/list_view_portrait.png) + +Landscape: + +!["List View demo in landscape."](assets/canonical_layouts/list_view_landscape.png) + +This demo is an example of an email inbox layout, where there's a list of items +that can be clicked to show a detailed view. + +In portrait it shows only the list of emails, but in landscape it also shows a +selected email view by its side. + +### Implementation + +Source code: + +* [`AdaptiveListViewDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveListViewDemoActivity.java) +* [`AdaptiveListViewDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveListViewDemoFragment.java) +* [`AdaptiveListViewDetailDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveListViewDemoFragment.java) + +This demo differs a bit from the others because it uses two fragments. + +**AdaptiveListViewDemoActivity.java** + +The `AdaptiveListViewDemoActivity` follows the logic described in the +[general implementation](#general-implementation) section above. + +The Activity either displays an `AdaptiveListViewDemoFragment` that fills the +screen (portrait layout) or both an `AdaptiveListViewDemoFragment` and an +`AdaptiveListViewDetailDemoFragment` side by side (landscape layout). + +In its `StateContainer` class, it updates the layout according to the device +orientation, and if it's in landscape, it also checks for a vertical fold in +order to update the position of the vertical `ReactiveGuide`. If there's no +vertical fold then the guideline is positioned at the middle of the screen. + +**AdaptiveListViewDemoFragment.java** + +The `AdaptiveListViewDemoFragment` class represents the email list. It inflates +`cat_adaptive_list_view_fragment.xml` and sets up the `emailList` recycler view. +It also contains the mock `Email` data and class. + +In its `emailAdapterListener`, it creates an instance of +`AdaptiveListViewDetailDemoFragment` and displays it either in its own container +if the device is in portrait, or in the `id/list_view_detail_fragment_container` +from the Activity XML if it's in landscape. + +**AdaptiveListViewDetailDemoFragment.java** + +The `AdaptiveListViewDetailDemoFragment` class represents an opened email view. +It inflates `cat_adaptive_list_view_detail_fragment.xml` and updates the email +title according to the `emailId`. + +**Note:** You can also implement this demo to achieve something similar would be +with +[`SlidingPaneLayout`](https://developer.android.com/guide/topics/ui/layout/twopane). + +## Feed demo + +The following shows screenshots of the Feed demo in different devices and screen +sizes. + +Portrait: + +!["Feed demo in portrait."](assets/canonical_layouts/feed_portrait.png) + +Landscape: + +!["Feed demo in landscape."](assets/canonical_layouts/feed_landscape.png) + +Open foldable in portrait: + +!["Feed demo in an open foldable and in portrait."](assets/canonical_layouts/feed_foldable.png) + +This demo is an example of a news feed layout. + +### Implementation + +Source code: + +* [`AdaptiveFeedDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveFeedDemoActivity.java) +* [`AdaptiveFeedDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveFeedDemoFragment.java) + +**AdaptiveFeedDemoFragment.java** + +The `AdaptiveFeedDemoFragment` class inflates `cat_adaptive_feed_fragment.xml` +and sets up two `RecyclerView`s, a `smallContentList` that holds small cards and +a `largeContentList` that holds large ones. + +The Fragment also sets up two constraint sets, a `closedLayout` and an +`openLayout`. + +For non-foldable devices, they are displayed as follows: + +Orientation | Layout +----------- | -------------- +Portrait | `closedLayout` +Landscape | `openLayout` + +For foldables: + +Orientation | State | Layout +----------- | ------ | -------------- +Portrait | closed | `closedLayout` +Portrait | open | `openLayout` +Landscape | any | `openLayout` + +The default layout is the **`closedLayout`**, which displays +`cat_adaptive_feed_fragment.xml`. There, the `largeContentList` has +`android:visibility="gone"`, as it only displays one large card (with id +`highlight_content_card`). + +The **`openLayout`** hides the `highlight_content_card`, displays the +`largeContentList`, and rearranges the position of the different components. It +does so by using the `ReactiveGuide` in the XML as reference. + +For example, the `MaterialButton` has +`app:layout_constraintStart_toStartOf="parent"` by default, so we change its +position by calling + +```java +constraintSet.connect(R.id.top_button, ConstraintSet.START, R.id.fold, ConstraintSet.END) +``` + +making it appear on the right side of the guideline. + +We change the position of the `ReactiveGuide` by either setting it at the fold +position if it's a vertical fold, or by setting it to the middle of the screen +if it's not or if the device isn't foldable. + +**AdaptiveFeedDemoActivity.java** + +The `AdaptiveFeedDemoActivity` follows the logic described in the +[general implementation](#general-implementation) section above. + +In its `StateContainer` class, it calls the `AdaptiveFeedDemoFragment` methods +responsible for updating the layout and the `ReactiveGuide` position. + +## Single View Hero demo + +The following shows screenshots of the Single View Hero demo in different +devices and screen sizes. + +Portrait: + +!["Single View Hero demo in portrait."](assets/canonical_layouts/hero_portrait.png) + +Landscape: + +!["Single View Hero demo landscape in landscape."](assets/canonical_layouts/hero_landscape.png) + +This demo is an example of a layout that shows a main content view followed by a +list view of supporting items. In bigger screens it also reveals a large top +content view on top, also called a hero view. + +### Implementation + +Source code: + +* [`AdaptiveHeroDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveHeroDemoActivity.java) +* [`AdaptiveHeroDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveHeroDemoFragment.java) + +**AdaptiveHeroDemoFragment.java** + +The `AdaptiveHeroDemoFragment` class inflates `cat_adaptive_hero_fragment.xml` +and sets up a recycler view `sideContentList`, and three different constrain +sets, `smallLayout`, `mediumLayout`, and `largeLayout`. + +The change of layouts is based exclusively on screen size: + +Size | Layout +------ | -------------- +small | `smallLayout` +medium | `mediumLayout` +large | `largeLayout` + +The default layout is the **`smallLayout`**, which displays +`cat_adaptive_hero_fragment.xml`. + +The **`mediumLayout`** changes the `sideContentList` position from below the +main content card to its right. It also displays a large top content card at the +top of both views. + +The **`largeLayout`** changes the `sideContentList` position again, but it +displays at the right side of the top content card view instead of below it. + +**AdaptiveHeroDemoActivity.java** + +The `AdaptiveHeroDemoActivity` follows the logic described in the +[general implementation](#general-implementation) section above, but since its +changes are based only on screen size, it does not worry about foldable states. + +## Supporting Panel demo + +The following shows screenshots of the Supporting Panel demo in different +devices and screen sizes. + +Portrait: + +!["Supporting Panel demo in portrait."](assets/canonical_layouts/supporting_panel_portrait.png) + +Landscape: + +!["Supporting Panel demo in landscape."](assets/canonical_layouts/supporting_panel_landscape.png) + +This demo is an example of a layout that has an always visible main content +view, followed by a supporting panel with a list of items that changes position +depending on certain screen configurations. + +### Implementation + +Source code: + +* [`AdaptiveSupportingPanelDemoActivity`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveSupportingPanelDemoActivity.java) +* [`AdaptiveSupportingPanelDemoFragment`](https://github.com/material-components/material-components-android/tree/master/catalog/java/io/material/catalog/adaptive/AdaptiveSupportingPanelDemoFragment.java) + +**AdaptiveSupportingPanelDemoFragment.java** + +The `AdaptiveSupportingPanelDemoFragment` class inflates +`cat_adaptive_supporting_panel_fragment.xml` and sets up a recycler view +`supportingPanelList`, and three constraint sets, `portraitLayout`, +`landscapeLayout`, and `tableTopLayout`. + +The change of layouts is based on device orientation: + +Orientation | Layout +----------- | ----------------- +portrait | `portraitLayout` +landscape | `landscapeLayout` + +For foldables, it also depends on state and fold orientation: + +Orientation | FoldingFeature.State | FoldingFeature.Orientation | Layout +----------- | -------------------- | -------------------------- | ------ +any | `HALF.OPENED` | `HORIZONTAL` | `tableTopLayout` +any | `HALF.OPENED` | `VERTICAL` | `portraitLayout` +portrait | `FLAT` or none | any | `portraitLayout` +landscape | `FLAT` or none | any | `landscapeLayout` + +The default layout is the **`portraitLayout`**, looking like the +`cat_adaptive_supporting_panel_fragment.xml` as it is. + +The **`landscapeLayout`** changes the `supportingPanelList` position from below +the main content to its right. + +The **`tableTopLayout`** is similar to the `portraitLayout`, but it makes sure +that the main content is limited to being displayed above the fold, while the +`supportingPanelList` is displayed below it. + +We change the position of the `ReactiveGuide` by setting it at the fold when the +device is in table top mode. + +**AdaptiveSupportingPanelDemoActivity.java** + +The `AdaptiveSupportingPanelDemoActivity` follows the logic described in the +[general implementation](#general-implementation) section above. + +In its `StateContainer` class, it calls the +`AdaptiveSupportingPanelDemoFragment` methods responsible for updating the +layout. diff --git a/docs/adaptive/assets/canonical_layouts/feed_foldable.png b/docs/adaptive/assets/canonical_layouts/feed_foldable.png new file mode 100644 index 00000000000..ffa0a02fd36 Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/feed_foldable.png differ diff --git a/docs/adaptive/assets/canonical_layouts/feed_landscape.png b/docs/adaptive/assets/canonical_layouts/feed_landscape.png new file mode 100644 index 00000000000..f399f974442 Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/feed_landscape.png differ diff --git a/docs/adaptive/assets/canonical_layouts/feed_portrait.png b/docs/adaptive/assets/canonical_layouts/feed_portrait.png new file mode 100644 index 00000000000..37ec90edcfe Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/feed_portrait.png differ diff --git a/docs/adaptive/assets/canonical_layouts/hero_landscape.png b/docs/adaptive/assets/canonical_layouts/hero_landscape.png new file mode 100644 index 00000000000..bb9697b3c93 Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/hero_landscape.png differ diff --git a/docs/adaptive/assets/canonical_layouts/hero_portrait.png b/docs/adaptive/assets/canonical_layouts/hero_portrait.png new file mode 100644 index 00000000000..6d8e8328f51 Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/hero_portrait.png differ diff --git a/docs/adaptive/assets/canonical_layouts/list_view_landscape.png b/docs/adaptive/assets/canonical_layouts/list_view_landscape.png new file mode 100644 index 00000000000..5ef616a7f98 Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/list_view_landscape.png differ diff --git a/docs/adaptive/assets/canonical_layouts/list_view_portrait.png b/docs/adaptive/assets/canonical_layouts/list_view_portrait.png new file mode 100644 index 00000000000..bb60db1e492 Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/list_view_portrait.png differ diff --git a/docs/adaptive/assets/canonical_layouts/supporting_panel_landscape.png b/docs/adaptive/assets/canonical_layouts/supporting_panel_landscape.png new file mode 100644 index 00000000000..1fe57281faf Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/supporting_panel_landscape.png differ diff --git a/docs/adaptive/assets/canonical_layouts/supporting_panel_portrait.png b/docs/adaptive/assets/canonical_layouts/supporting_panel_portrait.png new file mode 100644 index 00000000000..fbcb76a416b Binary files /dev/null and b/docs/adaptive/assets/canonical_layouts/supporting_panel_portrait.png differ