exprollable_page_view 1.0.0-beta.5 copy "exprollable_page_view: ^1.0.0-beta.5" to clipboard
exprollable_page_view: ^1.0.0-beta.5 copied to clipboard

Yet another PageView widget that expands its viewport as it scrolls. Exprollable is a coined word combining the words expandable and scrollable.

Pub

English|日本語

ExprollablePageView #

Yet another PageView widget that expands its viewport as it scrolls. 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: (Youtube)

demo

Index #

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

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 widget 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 PageView's docs 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 is used by the internal PageView. It also controlls how the ExprollablePageView changes its viewport as it scrolls.

  ExprollablePageController({
    super.initialPage,
    super.keepPage,
    double minViewportFraction = 0.9,
    bool overshootEffect = false,
    ViewportOffset initialViewportOffset = ViewportOffset.shrunk,
    ViewportOffset maxViewportOffset = ViewportOffset.shrunk,
    List<ViewportOffset> snapViewportOffsets = const [
      ViewportOffset.expanded,
      ViewportOffset.shrunk,
    ],
  });
  • initialPage: The page to show when first creating the ExprollablePageView.

  • minViewportFraction: The minimum fraction of the viewport that each page should occupy. It must be between 0.0 ~ 1.0.

  • initialViewportOffset: The initial offset of the viewport.

  • maxViewportOffset: The maximum offset of the viewport. Typically used with custom snapViewportOffsets.

  • snapViewportOffsets: A list of offsets to snap the viewport to. An example of this feature can be found in make the PageView like a BottomSheet section.

  • overshootEffect: Indicates if overshoot effect is enabled. See Overshoot effect section for more details.

Viewport fraction and offset

The state of the viewport is described by the 2 mesurements: fraction and offset. A fraction indicates how much space each page should occupy in the viewport, and it must be between 0.0 and 1.0. An offset is the distance from the top of the viewport to the top of the current page.

viewport-fraction-offset

ViewportOffset is a class that represents an offset. It has 2 pre-defined offsets, ViewportOffset.expanded and ViewportOffset.shrunk, at which the viewport fraction is 1.0 and the minimum, respectively. It also has a factory constructor ViewportOffset.fractional that creates an offset from a fractional value. For example, ViewportOffset.fractional(1.0) is equivalent to ViewportOffset.shrunk, and ViewportOffset.fractional(0.0) matches the bottom of the viewport. Some examples of the use of this class can be found in make the PageView like a BottomSheet, observe the state of the viewport.

viewport-offsets

Overshoot effect

If ExprollablePageController.overshootEffect is enabled, the upper segment of the current page will slightly exceed the top of the viewport when it goes fullscreen. To be precise, this means that the viewport offset will take a negative value when the viewport fraction is 1.0. This trick creates a dynamic visual effect when the page goes fullscreen. The figures below are a demonstration of how the overshoot effect affects (disabled in the left, enabled in the right).

overshoot-disabled overshoot-enabled

Overshoot effect will works correctly only if:

  • MediaQuery.padding.bottom > 0
  • Ther lower segment of ExprollablePageView is behind a widget such as NavigationBar, 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(overshootEffect: true);

Widget build(BuildContext context) {
  return Scaffold(
    extendBody: true,
    bottomNavigationBar: BottomNavigationBar(...),
    body: ExprollablePageView(
      controller: controller,
      itemBuilder: (context, page) { ... },
    ),
  );
}

ModalExprollable #

Use ModalExprollable to create modal dialog style PageViews. 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(...);
    },
  );

modal-exprollable

Slidable list items #

One of the advantages of ExprollablePageView over built-in PageView is that widgets with horizontal slide action such as flutter_slidable can be used within each page. You can see an example that uses flutter_slidable in example/lib/src/complex_example/album_details.dart.

SlideActionDemo

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 PageView like a BottomSheet? #

Use ExprollablePageController . Below is an example controller for snapping to the three states:

  1. The viewport is completely expanded (viewportFraction == 1.0)
  2. The viewport is slightly smaller than the screen (viewportFraction == 0.9)
  3. viewportFraction == 0.9 and the PageView covers only half of the screen like BottomSheet.

For complete code, see custom_snap_offsets_example.dart.

const peekOffset = ViewportOffset.fractional(0.5);
controller = ExprollablePageController(
  minViewportFraction: 0.9,
  initialViewportOffset: peekOffset,
  maxViewportOffset: peekOffset,
  snapViewportOffsets: [
    ViewportOffset.expanded,
    ViewportOffset.shrunk,
    peekOffset,
  ],
);

observe the state of the viewport? #

There are 3 ways to observe changes of the viewport state.

1. Listen ExprollablePageController.viewport

ExprollablePageController.viewport is a ValueListenable<PageViewportMetrics> and PageViewportMetrics 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 PageViewportMetrics vp = controller.viewport.value;
  final bool isShrunk = vp.isShrunk;
  final bool isExpanded = vp.isExpanded;
});

2. Listen PageViewportUpdateNotification

ExprollablePageView dispatches PageViewportUpdateNotification every time its state changes, and it contains a PageViewportMetrics. 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<PageViewportUpdateNotification>(
        onNotification: (notification) {
          final PageViewportMetrics 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: (PageViewportMetrics vp) {...},
);

add space between pages? #

Just wrap each page with PageGutter.

ExprollablePageView(
  itemBuilder: (context, page) {
    return PageGutter(
      gutterWidth: 12,
      child: ListView(...),
    );
  },
);

prevent my AppBar going off the screen when overshootEffect 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.animateViewportOffsetTo.

controller.animateViewportOffsetTo(
  ViewportOffset.shrunk,
  curve: Curves.easeInOutSine,
  duration: Duration(seconds: 1),
);

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!

143
likes
0
pub points
86%
popularity

Publisher

verified publishernorelease.dev

Yet another PageView widget that expands its viewport as it scrolls. Exprollable is a coined word combining the words expandable and scrollable.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, meta

More

Packages that depend on exprollable_page_view