diff --git a/example/lib/src/common.dart b/example/lib/src/common.dart index 6485933..ffb7bab 100644 --- a/example/lib/src/common.dart +++ b/example/lib/src/common.dart @@ -36,8 +36,7 @@ class _ExampleListViewState extends State { itemCount: 50, itemBuilder: (_, index) { return ListTile( - onTap: () => - debugPrint("onTap(index=$index, page=${widget.page})"), + onTap: () => debugPrint("onTap(index=$index, page=${widget.page})"), title: Text("Item#$index"), subtitle: Text("Page#${widget.page}"), ); diff --git a/example/lib/src/custom_snap_offsets_example.dart b/example/lib/src/custom_snap_offsets_example.dart index 2c8742a..986df1c 100644 --- a/example/lib/src/custom_snap_offsets_example.dart +++ b/example/lib/src/custom_snap_offsets_example.dart @@ -10,22 +10,14 @@ class CustomSnapOffsetsExample extends StatefulWidget { _CustomSnapOffsetsExampleState(); } -class _CustomSnapOffsetsExampleState - extends State { +class _CustomSnapOffsetsExampleState extends State { late final ExprollablePageController controller; @override void initState() { super.initState(); - const peekOffset = ViewportOffset.fractional(0.5); - controller = ExprollablePageController( - initialViewportOffset: peekOffset, - maxViewportOffset: peekOffset, - snapViewportOffsets: [ - ViewportOffset.expanded, - ViewportOffset.shrunk, - peekOffset, - ], + controller = ExprollablePageController.withAdditionalSnapOffsets( + const [ViewportOffset.fractional(0.5)], ); } diff --git a/package/CHANGELOG.md b/package/CHANGELOG.md index 118e06e..ff980e1 100644 --- a/package/CHANGELOG.md +++ b/package/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.0.0-beta.7 30-04-2023 + +- Fix #20 +- Add a convenience constructor `ExprollablePageController.withAdditionalSnapOffsets` (proposed in #21) +- Improve the documents + ## 1.0.0-beta.6 19-04-2023 - Fix #17 diff --git a/package/README.md b/package/README.md index 1146ab9..3c8afa8 100644 --- a/package/README.md +++ b/package/README.md @@ -1,6 +1,6 @@ [English](https://github.com/fujidaiti/exprollable_page_view/blob/master/package/README.md)|[日本語](https://github.com/fujidaiti/exprollable_page_view/blob/master/package/res/README.jp.md) -[![Pub](https://img.shields.io/pub/v/exprollable_page_view.svg?logo=flutter&color=blue&style=flat-square)](https://pub.dev/packages/exprollable_page_view) [![Docs](https://img.shields.io/badge/-API%20Reference-orange?style=flat-square)](https://pub.dev/documentation/exprollable_page_view/latest/) +[![Pub](https://img.shields.io/pub/v/exprollable_page_view.svg?logo=flutter&color=blue)](https://pub.dev/packages/exprollable_page_view) [![Pub Popularity](https://img.shields.io/pub/popularity/exprollable_page_view)](https://pub.dev/packages/exprollable_page_view) [![Docs](https://img.shields.io/badge/-API%20Reference-orange)](https://pub.dev/documentation/exprollable_page_view/latest/) # ExprollablePageView @@ -8,7 +8,7 @@ Yet another PageView widget that expands its viewport as it scrolls. **Exprollab Here is an example of what you can do with this widget: -![demo](https://user-images.githubusercontent.com/68946713/231328800-03038dc6-19e8-4c7c-933b-7e7436ba6619.gif) ![demo2](https://user-images.githubusercontent.com/68946713/234313845-caa8dd75-c9e2-4fd9-b177-f4a6795c4802.gif) + ## Index @@ -259,6 +259,12 @@ controller = ExprollablePageController( ); ``` +You can also use `ExprollablePageController.withAdditionalSnapOffsets` as a shorthand. The following snippet is equevalent to the above: + +```dart +controller = ExprollablePageController.withAdditionalSnapOffsets([peekOffset]); +``` + ### observe the state of the viewport? There are 3 ways to observe changes of the viewport state. diff --git a/package/lib/src/addon/modal.dart b/package/lib/src/addon/modal.dart index 24681f3..fe0a251 100644 --- a/package/lib/src/addon/modal.dart +++ b/package/lib/src/addon/modal.dart @@ -4,7 +4,7 @@ import 'package:exprollable_page_view/src/core/controller.dart'; import 'package:exprollable_page_view/src/core/view.dart'; import 'package:flutter/material.dart'; -/// Show a [ExprollablePageView] as a modal dialog. +/// Show an [ExprollablePageView] as a modal dialog. Future showModalExprollable( BuildContext context, { required WidgetBuilder builder, @@ -16,7 +16,7 @@ Future showModalExprollable( Color initialBarrierColor = Colors.black54, void Function(BuildContext) dismissBehavior = _defaultDismissBehavior, bool barrierDismissible = true, - ViewportOffset dismissThresholdOffset = const ViewportOffset.fractional(0.18), + ViewportOffset dismissThresholdOffset = const ViewportOffset.fractional(0.1), }) => showDialog( context: context, @@ -42,7 +42,7 @@ Future showModalExprollable( void _defaultDismissBehavior(BuildContext context) => Navigator.of(context).pop(); -/// A widget that makes a [ExprollablePageView] modal dialog style. +/// A widget that makes a modal dialog style [ExprollablePageView]. /// /// This widget adds a translucent background (barrier) and /// *swipe down to dismiss* action to the child page view. @@ -61,7 +61,7 @@ class ModalExprollable extends StatefulWidget { this.initialBarrierColor = Colors.black54, this.dismissBehavior = _defaultDismissBehavior, this.barrierDismissible = true, - this.dismissThresholdOffset = const ViewportOffset.fractional(0.18), + this.dismissThresholdOffset = const ViewportOffset.fractional(0.1), }) : assert(dismissThresholdOffset > ViewportOffset.shrunk); /// Called when the dialog should be dismissed. @@ -170,11 +170,128 @@ class _ModalExprollableState extends State { ), ); - return Stack( - children: [ - Positioned.fill(child: barrier), - Positioned.fill(child: pageView), - ], + return ScrollConfiguration( + behavior: const _ModalExprollableScrollBehavior(), + child: Stack( + children: [ + Positioned.fill(child: barrier), + Positioned.fill(child: pageView), + ], + ), ); } } + +/// Scroll physics commonly used for descendant scrollables of [ModalExprollable]. +/// +/// This physics always lets the user overscroll making *dra down to dismiss* action +/// is available on every platform. [ModalExprollable] provides this as the default physics +/// for its descendants via [ScrollConfiguration]. +/// If you explicitly specify a physics for a descendant scrollable, +/// consider to wrap that physics with this. +/// +/// ```dart +/// final physics = const ModalExprollableScrollPhysics( +/// parnet: ClampScrollPhysics(), +/// ); +/// ``` +class ModalExprollableScrollPhysics extends ScrollPhysics { + /// Creates a scrolling physics that always lets the user overscroll. + /// + /// This will delegate its logic to [BouncingScrollPhysics] if the user overscrolls, + /// so that the *drag down to dismiss* action is available on every platform. + /// Otherwise, it delegates to the given [parent]. + const ModalExprollableScrollPhysics({ + ScrollPhysics? parent, + }) : super(parent: parent); + + @override + ModalExprollableScrollPhysics applyTo(ScrollPhysics? ancestor) { + return ModalExprollableScrollPhysics(parent: buildParent(ancestor)); + } + + @override + bool get allowImplicitScrolling => + parent?.allowImplicitScrolling ?? super.allowImplicitScrolling; + + @override + double adjustPositionForNewDimensions({ + required ScrollMetrics oldPosition, + required ScrollMetrics newPosition, + required bool isScrolling, + required double velocity, + }) { + if (parent == null || + _outOfRange(oldPosition) || + _outOfRange(newPosition)) { + return const BouncingScrollPhysics().adjustPositionForNewDimensions( + oldPosition: oldPosition, + newPosition: newPosition, + isScrolling: isScrolling, + velocity: velocity, + ); + } else { + return parent!.adjustPositionForNewDimensions( + oldPosition: oldPosition, + newPosition: newPosition, + isScrolling: isScrolling, + velocity: velocity, + ); + } + } + + @override + Simulation? createBallisticSimulation( + ScrollMetrics position, + double velocity, + ) { + return velocity < 0 + ? const BouncingScrollPhysics() + .createBallisticSimulation(position, velocity) + : super.createBallisticSimulation(position, velocity); + } + + @override + double applyBoundaryConditions(ScrollMetrics position, double value) { + if (parent == null || + _outOfRange(position) || + _outOfRange(position.copyWith(pixels: value))) { + return const BouncingScrollPhysics() + .applyBoundaryConditions(position, value); + } else { + return parent!.applyBoundaryConditions(position, value); + } + } + + @override + double applyPhysicsToUserOffset(ScrollMetrics position, double offset) { + if (parent == null || + _outOfRange(position) || + _outOfRange(position.copyWith( + pixels: position.pixels + offset, + ))) { + return const BouncingScrollPhysics() + .applyPhysicsToUserOffset(position, offset); + } else { + return parent!.applyPhysicsToUserOffset(position, offset); + } + } + + bool _outOfRange(ScrollMetrics position) { + return position.hasPixels && + position.hasContentDimensions && + position.pixels < position.minScrollExtent; + } +} + +class _ModalExprollableScrollBehavior extends ScrollBehavior { + const _ModalExprollableScrollBehavior(); + + @override + ScrollPhysics getScrollPhysics(BuildContext context) { + final defaultPhysics = super.getScrollPhysics(context); + return defaultPhysics is BouncingScrollPhysics + ? defaultPhysics + : ModalExprollableScrollPhysics(parent: defaultPhysics); + } +} diff --git a/package/lib/src/core/controller.dart b/package/lib/src/core/controller.dart index 0613cff..234f8cb 100644 --- a/package/lib/src/core/controller.dart +++ b/package/lib/src/core/controller.dart @@ -123,6 +123,41 @@ class ExprollablePageController extends PageController { _currentPage = _CurrentPageNotifier(controller: this); } + /// Crate a page controller with additional snap viewport offsets. + /// + /// [additionalSnapOffsets] must not be empty. The viewport will snap to + /// the offsets given by [additionalSnapOffsets] in addition to + /// [ViewportOffset.expanded] and [ViewportOffset.shrunk]. + /// + /// If [initialViewportOffset] or [maxViewportOffset] is not specified, + /// the max offset in [additionalSnapOffsets] is used. + factory ExprollablePageController.withAdditionalSnapOffsets( + List additionalSnapOffsets, { + int initialPage = 0, + bool keepPage = true, + double minViewportFraction = 0.9, + bool overshootEffect = false, + ViewportOffset? initialViewportOffset, + ViewportOffset? maxViewportOffset, + }) { + assert(additionalSnapOffsets.isNotEmpty); + final snapViewportOffsets = { + ViewportOffset.expanded, + ViewportOffset.shrunk, + ...additionalSnapOffsets, + }.toList() + ..sort(); + return ExprollablePageController( + initialPage: initialPage, + keepPage: keepPage, + minViewportFraction: minViewportFraction, + overshootEffect: overshootEffect, + initialViewportOffset: initialViewportOffset ?? snapViewportOffsets.last, + maxViewportOffset: maxViewportOffset ?? snapViewportOffsets.last, + snapViewportOffsets: snapViewportOffsets, + ); + } + final _absorberGroup = ScrollAbsorberGroup(); final Map _contentScrollControllers = {}; diff --git a/package/pubspec.yaml b/package/pubspec.yaml index 01898c3..22e974b 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -1,6 +1,6 @@ name: exprollable_page_view description: Yet another PageView widget that expands its viewport as it scrolls. Exprollable is a coined word combining the words expandable and scrollable. -version: 1.0.0-beta.6 +version: 1.0.0-beta.7 repository: https://github.com/fujidaiti/exprollable_page_view environment: