Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 1.0.0-beta.7 #23

Merged
merged 7 commits into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions example/lib/src/common.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ class _ExampleListViewState extends State<ExampleListView> {
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}"),
);
Expand Down
14 changes: 3 additions & 11 deletions example/lib/src/custom_snap_offsets_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,14 @@ class CustomSnapOffsetsExample extends StatefulWidget {
_CustomSnapOffsetsExampleState();
}

class _CustomSnapOffsetsExampleState
extends State<CustomSnapOffsetsExample> {
class _CustomSnapOffsetsExampleState extends State<CustomSnapOffsetsExample> {
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)],
);
}

Expand Down
6 changes: 6 additions & 0 deletions package/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
10 changes: 8 additions & 2 deletions package/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[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

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](https://www.apple.com/jp/apple-books/) app on iOS.

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)
<img width="320" src="https://user-images.githubusercontent.com/68946713/231328800-03038dc6-19e8-4c7c-933b-7e7436ba6619.gif"> <img width="320" src="https://user-images.githubusercontent.com/68946713/234313845-caa8dd75-c9e2-4fd9-b177-f4a6795c4802.gif">

## Index

Expand Down Expand Up @@ -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.
Expand Down
135 changes: 126 additions & 9 deletions package/lib/src/addon/modal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<T?> showModalExprollable<T>(
BuildContext context, {
required WidgetBuilder builder,
Expand All @@ -16,7 +16,7 @@ Future<T?> showModalExprollable<T>(
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<T>(
context: context,
Expand All @@ -42,7 +42,7 @@ Future<T?> showModalExprollable<T>(
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.
Expand All @@ -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.
Expand Down Expand Up @@ -170,11 +170,128 @@ class _ModalExprollableState extends State<ModalExprollable> {
),
);

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);
}
}
35 changes: 35 additions & 0 deletions package/lib/src/core/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ViewportOffset> 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<int, PageContentScrollController> _contentScrollControllers = {};

Expand Down
2 changes: 1 addition & 1 deletion package/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down