exprollable_page_view 1.0.0-rc.1 exprollable_page_view: ^1.0.0-rc.1 copied to clipboard
Yet another PageView widget that expands its page while scrolling it. Exprollable is a coined word combining the words expandable and scrollable.
ExprollablePageView #
Yet another PageView widget that expands the viewport of the current page while scrolling it. Exprollable is a coined word combining the words expandable and scrollable. This project is an attemt to clone a modal sheet UI used in Apple Books app on iOS.
Here is an example of what you can do with this widget:
Announcement #
17-05-2023 #
Version 1.0.0-rc.1 has been released 🎉. This version includes several breaking changes, so if you are already using ^1.0.0-beta, you may need to migrate according to Migration Guide.
Index #
- ExprollablePageView
Try it #
Run the example application and explore the all features of this package.
git clone git@github.com:fujidaiti/exprollable_page_view.git
cd example
flutter pub get
flutter run
There is another example, which demonstrates how ExprollablePageView
is able to work with Google Maps. See maps_example/README for more details.
Install #
Add this package to your project using pub
command.
flutter pub add exprollable_page_view
Usage #
See how-to section if you are looking for specific usages.
ExprollablePageView #
You can use ExprollablePageView
just as built-in PageView
as bellow. ExprollablePageView
works with any scrollable widgets that can accept a ScrollController
. Note, however, that it will not work as expected unless you use a ScrollController
obtained from PageContentScrollController.of
.
import 'package:exprollable_page_view/exprollable_page_view.dart';
@override
Widget build(BuildContext context) {
return Scaffold(
body: ExprollablePageView(
itemCount: 5,
itemBuilder: (context, page) {
return ListView.builder(
controller: PageContentScrollController.of(context),
itemBuilder: (context, index) {
return ListTile(title: Text('Item#$index'));
},
);
},
),
);
}
The constructor of ExprollablePageView
has almost the same signature as PageView.builder
. See the document of PageView for more details on each parameter.
const ExprollablePageView({
Key? key,
IndexedWidgetBuilder itemBuilder,
int? itemCount,
ExprollablePageController? controller,
bool reverse = false,
ScrollPhysics? physics,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
bool allowImplicitScrolling = false,
String? restorationId,
Clip clipBehavior = Clip.hardEdge,
ScrollBehavior? scrollBehavior,
bool padEnds = true,
void Function(PageViewportMetrics metrics)? onViewportChanged,
void Function(int page)? onPageChanged,
});
ExprollablePageController #
A subclass of PageController
that will be attached to the internal PageView
. It also controlls how the viewport of the current page changes along with vertical scrolling.
final controller = ExprollablePageController(
initialPage: 0,
viewportConfiguration: ViewportConfiguration(
minFraction: 0.9,
),
);
Specify a ViewportConfiguration
with the desired values to tweak the behavior of the page view.
factory ViewportConfiguration({
bool overshootEffect = false,
double minFraction = 0.9,
double maxFraction = 1.0,
ViewportInset shrunkInset = ViewportInset.shrunk,
ViewportInset? initialInset,
List<ViewportInset> extraSnapInsets = const [],
});
minFraction
: The fraction of the viewport that each page should occupy when it is shrunk by vertical scrolling.initialInset
: The initial viewport inset.shrunkInset
: A viewport inset at which the current page is fully shrunk.extraSnapInsets
: A list of extra insets the viewport will snap to. An example of the use of this feature can be found in make the page view like a BottomSheet section.overshootEffect
: Indicates if overshoot effect is enabled. See Overshoot effect section for more details.
Viewport fraction and inset
The state of the viewport is described by the 2 mesurements: fraction and inset. The fraction indicates how much space each page should occupy in the viewport, and the inset is the distance from the top of the viewport to the top of the current page viewport. These measurements are managed in Viewport
class, and can be referenced via the controller as it is exposed as ExprollablePageController.viewport
. See observe the vewport state section for more details.
ViewportInset
is a class that represents an inset. There are 3 predefined ViewportInset
s:
ViewportInset.expanded
: The default inset at which the current page is fully expanded.ViewportInset.shrunk
: The default inset at which the current page is fully shrunk.ViewportInset.overshoot
: The default inset at which the current page is fully expanded and overshot (see Overshoot effect).
User defined insets can be created using ViewportInset.fixed
and ViewportInset.fractional
, or you can extend ViewportInset
to perform more complex calculations. Some examples of the use of this class can be found in make the PageView like a BottomSheet, observe the state of the viewport.
Overshoot effect
If the overshoot effect is enabled, the upper segment of the current page viewport will slightly exceed the top of the viewport when it goes fullscreen. To be precise, this means that the viewport inset will take a negative value when the viewport fraction is 1. This trick creates a dynamic visual effect when the page goes fullscreen. The figures below are demonstrations of how the overshoot effect affects (disabled in the left, enabled in the right).
Overshoot effect will works correctly only if:
MediaQuery.padding.bottom
> 0- Ther lower segment of
ExprollablePageView
is behind a widget such asNavigationBar
,BottomAppBar
Perhaps the most common use is to wrap an ExprollablePageView
with a Scaffold
. In that case, do not forget to enable Scaffold.extentBody
and then everything should be fine.
controller = ExprollablePageController(
viewportConfiguration: ViewportConfiguration(
overshootEffect: true, // Enable the overshoot effect
),
);
Widget build(BuildContext context) {
return Scaffold(
extendBody: true, // Do not forget this line
bottomNavigationBar: BottomNavigationBar(...),
body: ExprollablePageView(
controller: controller,
itemBuilder: (context, page) { ... },
),
);
}
ModalExprollable #
Use ModalExprollable
to create modal dialog style page views. This widget adds a translucent background and swipe down to dismiss action to your ExprollablePageView
. You can use showModalExprollable
a convenience function that wraps your ExprollablePageView
with ModalExprollable
and display it as a dialog. If you want to customize reveal/dismiss behavior of the dialog, create your own PageRoute
and use ModalExprollable
in it.
showModalExprollable(
context,
builder: (context) {
return ExprollablePageView(...);
},
);
Slidable list items #
One of the advantages of ExprollablePageView
over the built-in PageView
is that widgets with horizontal slide action such as flutter_slidable can be used within a page. You can see an example that uses flutter_slidable in example/lib/src/complex_example/album_details.dart
.
How to #
get the curret page? #
Use ExprollablePageController.currentPage
.
final page = pageController.currentPage.value;
You can also observe changes in currentPage
as it is type of ValueListenable<int>
.
pageController.currentPage.addListener(() {
final page = pageController.currentPage.value;
});
ExprollablePageView.onPageChanged
is another option to track the current page, which is equivalent to the above solusion as it just listens ExprollablePageController.currentPage
internally.
ExprollablePageView(
onPageChanged: (page) { ... },
);
make the page view like a BottomSheet? #
Use ExprollablePageController
and ViewportConfiguration
. Below is an example controller for snapping to the three states:
- The page is completely expanded (
Viewport.fraction == 1.0
) - The page is slightly smaller than the viewport (
Viewport.fraction == 0.9
) Viewport.fraction == 0.9
and the page view covers only half of the screen like a bottom sheet.
For complete code, see custom_snap_offsets_example.dart.
controller = ExprollablePageController(
viewportConfiguration: ViewportConfiguration(
minFraction: 0.9,
extraSnapInsets: [
ViewportInset.fractional(0.5),
],
),
);
observe the viewport state? #
There are 3 ways to observe changes of the viewport state.
1. Listen ExprollablePageController.viewport
ExprollablePageController.viewport
is a ValueListenable<ViewportMetrics>
and ViewportMetrics
contains the current state of the viewport. Thus, you can listen and use it to perfom some actions that depend on the viewport state.
controller.viewport.addListener(() {
final ViewportMetrics vp = controller.viewport.value;
final bool isShrunk = vp.isPageShrunk;
final bool isExpanded = vp.isPageExpanded;
});
2. Listen ViewportUpdateNotification
ExprollablePageView
dispatches ViewportUpdateNotification
every time its state changes, and it contains a ViewportMetrics
. You can listen the notifications using NotificationListener
widget. Make sure that the NotificationListener
is an ancestor of the ExprollablePageView
in your widget tree. This method is useful when you want to perform a state dependent action, but do not have a controller.
NotificationListener<ViewportUpdateNotification>(
onNotification: (notification) {
final ViewportMetrics vp = notification.metrics;
return false;
},
child: ...,
3. Use onViewportChanged
callback
The constructor of ExprollablePageView
accepts a callback that is invoked whenever the viewport state changes.
ExprollablePageView(
onPageViewChanged: (ViewportMetrics vp) {...},
);
add space between pages? #
Just wrap each page with PageGutter
.
ExprollablePageView(
itemBuilder: (context, page) {
return PageGutter(
gutterWidth: 12,
child: ListView(...),
);
},
);
prevent my app bar going off the screen when overshoote ffect is true? #
Use AdaptivePagePadding
. This widget adds appropriate padding to its child according to the current viewpor offset. An example code is found in adaptive_padding_example.dart.
Container(
color: Colors.lightBlue,
child: AdaptivePagePadding(
child: SizedBox(
height: height,
child: const Placeholder(),
),
),
);
animate the viewport state? #
Use ExprollablePageController.animateViewportInsetTo
.
// Shrunk the current page with scroll animation in 1 second.
controller.animateViewportInsetTo(
ViewportInset.shrunk,
curve: Curves.easeInOutSine,
duration: Duration(seconds: 1),
);
Migration guide #
^1.0.0-beta to ^1.0.0-rc.1 #
With the release of version 1.0.0-rc.1, there are several breaking changes.
PageViewportMetrics update
PageViewportMetrics
mixin was merged into ViewportMetrics
mixin and now deleted, and some properties were renamed. Replace the symbols in your code according to the table below:
PageViewportMetrics
👉ViewportMetrics
PageViewportMetrics.isShrunk
👉ViewportMetrics.isPageShrunk
PageViewportMetrics.isExpanded
👉ViewportMetrics.isPageExpanded
PageViewportMetrics.xxxOffset
👉ViewportMetrics.xxxInset
(the all properties with suffixOffset
was renamed with the new suffixInset
)PageViewportMetrics.overshootEffect
was deleted
ViewportController update
ViewportController
class was renamed to PageViewport
and no longer mixins ViewportMetrics
.
ViewportOffset update
For ViewportOffset
and its inherited classes, the suffix Offset
was replaced with the new suffix Inset
, and 2 new inherited classes were introduced (see Viewport fraction and inset).
-
ViewportOffset
👉ViewportInset
-
ExpandedViewportOffset
👉DefaultExpandedViewportinset
-
ShrunkViewportOffset
👉DefaultShrunkViewportInset
ExprollablePageController update
With the introduction of ViewportConfiguration
, the signature of ExprollablePageController
's constructor was changed.
Before:
final controller = ExprollablePageController(
initialPage: 0,
minViewportFraction: 0.9,
overshootEffect: true,
initialViewportOffset: ViewportOffset.shrunk,
);
After:
final controller = ExprollablePageController(
initialPage: 0,
viewportConfiguration: ViewportConfiguration(
minFraction: 0.9,
overshootEffect: true,
initialInset: ViewportInset.shrunk,
),
);
In addition, ExprollablePageController.withAdditionalSnapOffsets
was removed, use ViewportConfiguration.extraSnapInsets
instead. See ExprollablePageController section for more details.
Before:
final controller = ExprollablePageController.withAdditionalSnapOffsets([
ViewportOffset.fractional(0.5),
]);
After:
final controller = ExprollablePageController(
viewportConfiguration: ViewportConfiguration(
extraSnapOffset: [ViewportInset.fractional(0.5)],
),
);
Other renamed classes
StaticPageViewportMetrics
👉StaticViewportMetrics
PageViewportUpdateNotification
👉ViewportUpdateNotification
PageViewport
👉Viewport
Questions #
If you have any question, feel free to ask them on the discussions page.
Contributing #
If you find any bugs or have suggestions for improvement, please create an issue or a pull request on the GitHub repository. Contributions are welcome and appreciated!