Flutter for Single-Page Scrollable Websites with Navigator 2.0 — Part 4: Ensure Visible
In the second and third part of this series, we explored implementing a single-page scrollable website (SPSW) whose sections are built lazily and have equal height. In this article, we will build an SPSW with sections that have varying and unpredictable heights before being laid out. Instead of building the sections lazily, all the sections will be built at once within a SingleChildScrollView
widget.
If you haven’t read the previous articles, I would strongly suggest starting from the first part of this series since the implementation details of the common widgets will not be mentioned in this part.
- Part 1: Introduction
- Part 2: Scroll to Position
- Part 3: Scroll to Page
- Part 4: Ensure Visible
- Part 5: Scroll to Index
- Part 6: Navigation
- Part 7: Query Params
A common example of this SPSW type is docsify which is a documentation site generator. Famous Flutter libraries such as Bloc, and Hive use this tool for documentation.
HomeScreen
In the first and second sample apps, the HomeScreen
had a Column
which included TopNavigationMenu
, and ColorsSection
as its children. In this sample app, the HomeScreen
is a Row
with a SideNavigationMenu
, and the ColorsSection
.
SideNavigationMenu
SideNavigationMenu
is very similar to the TopNavigationMenu
widget except that it lists all the navigation menu buttons on the vertical axis. Since the NavigationMenuButton
we use in this sample app is the same as in the previous sample, we will not repeat the implementation details in this article.
ColorsSection
ColorsSection
is a scrollable widget which lists all the sections that have varying sizes. In order to be able to scroll to a section programmatically, we need the size information of the sections that are listed before the destination section in the scroll direction. We can only get the size information when the sections are laid out. Hence, in this sample, we won’t be building the sections lazily.
Similar to the previous sample, ColorsSection
:
- listens to the color code values coming from different sources and scrolls to the corresponding color section programmatically if the source of the update is not scrolling.
- notifies the color code listeners when the first visible section (
trailingIndex
) changes or a shaped button is clicked.
Unlike the previous samples, in this sample app, in addition to the shaped buttons, every section has a title and 2 to 6 paragraphs containing up to 900 words of Lorem ipsum placeholder text.
Lorem ipsum text is randomly generated using the flutter_lorem
package. A unique seed is used for each color section to provide the same number of paragraphs and words on each build.
Listen to Color Code Update
In the initState
of the ColorsSection
widget we do the following:
- Prepare the
GlobalKey
list to be used for the color sections. - Scroll to the initial position based on the value in the
colorCodeNotifier
. We can’t scroll to a position in theinitState
method because the widget is not laid out when it is called. Thus, we will scroll in the next frame after the first build using theaddPostFrameCallback
. This will cause a scroll effect each time the webpage is launched by entering the URL on the address bar. - Subscribe to the
colorCode
updates and programmatically scroll to the target section if the source of the update is not scrolling.
In a SingleChildScrollView
widget, we can programmatically scroll to a destination section using the static Scrollable.ensureVisible
method.
“ensureVisible: Scrolls the scrollables that enclose the given context so as to make the given context visible.”
We will get the GlobalKey
of the destination section widget and provide the currentContext
of the GlobalKey
as a parameter to Scrollable.ensureVisible
method and scroll programmatically with animation.
Notify a Color Code Update
We want to notify the color code listeners according to the first visible section (trailingIndex
) changes as the user scrolls.
We provide all the sections to a Column
widget as its children and add the Column
widget as a direct child of a SingleChildScrollView
. Using NotificationListener
widget, we first filter UserScrollNotification
type of notifications as the user scrolls. Then, we will get the current offset from the notification to find the trailing index (first visible index).
To determine the trailing index when a scroll notification is received, we iteratively compare the cumulative height of the sections with the current offset. Starting from the first section, we add up each section’s height in a for-loop. The trailing index will be the first index where the cumulative height is larger than the offset of the section.
When we assigned a GlobalKey
to each section. The currentContext
property of the GlobalKey
provides the BuildContext
of the section. Using the size
property of BuildContext
we can get the height of each section.
Here is the full code for the ColorSections
widget:
Resizing the Browser Window
As of 2.2.0 stable version, Flutter framework doesn’t rebuild widgets when the browser window is resized due to performance optimization. In this sample app, when the browser window is resized, the app shows the wrong section, however, it gets fixed on the next user scroll events or button clicks. In my opinion, this is not good but ok behavior from a user experience point and does not require forcing build to correct pixels after window resize.
Conclusion
In this article, we learned how to live with building all the sections at once in a SingleChildScrollView
+ Column
. This is clearly the most undesired and expensive way of implementing scroll logic for single-page scrollable websites. However, if the number of sections is small, and the content of each section is not expensive this can still be a good option due to its simplicity. In the next article, we will see lazily building a scrollable widget with varying-sized sections like an illusion.
You can run this sample app by right-clicking on the main_003.03.dart
file in the project source code. If you liked this article, please press the clap button, and star the Github repository of the sample apps.