Skip to content

Commit c350e5c

Browse files
committed
feat: Introduce ClampingWithOverscrollPhysics scroll physics
1 parent 7835283 commit c350e5c

14 files changed

+210
-56
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.1.0-dev.1
2+
- Experimental:
3+
- Added `ClampingWithOverscrollPhysics` scroll physics, that together with indicator controller allows handling overscroll correctly.
4+
- The indicator's cancel duration is now based on the dragging progress.
5+
- Updated the the dart sdk constraints to `>=2.17.0 <4.0.0`
16
## 3.0.0
27
- **CustomRefreshIndicator**:
38
- Deprecated *indicatorFinalizeDuration*, *indicatorSettleDuration*, *indicatorCancelDuration* and *completeStateDuration* parameters in favor of *durations*.

example/lib/main.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:example/screens/programmatically_controlled_indicator_screen.dar
55
import 'package:example/widgets/web_frame.dart';
66
import 'package:flutter/material.dart';
77

8-
import 'screens/opacity_indicator_screen.dart';
8+
import 'screens/custom_material_indicator_screen.dart';
99
import 'screens/fetch_more_screen.dart';
1010
import 'screens/ice_cream_indicator_screen.dart';
1111
import 'screens/plane_indicator_screen.dart';
@@ -29,7 +29,7 @@ class MyApp extends StatelessWidget {
2929
home: const MainScreen(),
3030
builder: (context, child) => WebFrame(child: child),
3131
routes: {
32-
'/example': (context) => const OpacityIndicatorScreen(),
32+
'/example': (context) => const CustomMaterialIndicatorScreen(),
3333
'/plane': (context) => const PlaneIndicatorScreen(),
3434
'/ice-cream': (context) => const IceCreamIndicatorScreen(),
3535
'/presentation': (context) => const PresentationScreen(),
@@ -74,7 +74,7 @@ class MainScreen extends StatelessWidget {
7474
child: Container(
7575
height: 50,
7676
alignment: Alignment.center,
77-
child: const Text("Multidirectional indicator"),
77+
child: const Text("Multidirectional Indicator"),
7878
),
7979
onPressed: () => Navigator.pushNamed(
8080
context,
@@ -87,7 +87,7 @@ class MainScreen extends StatelessWidget {
8787
height: 50,
8888
alignment: Alignment.center,
8989
child:
90-
const Text("Custom material indicator with list opacity"),
90+
const Text("Custom Material Indicator"),
9191
),
9292
onPressed: () => Navigator.pushNamed(
9393
context,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
2+
import 'package:example/widgets/example_app_bar.dart';
3+
import 'package:example/widgets/example_list.dart';
4+
import 'package:flutter/material.dart';
5+
6+
class CustomMaterialIndicatorScreen extends StatefulWidget {
7+
const CustomMaterialIndicatorScreen({super.key});
8+
9+
@override
10+
State<CustomMaterialIndicatorScreen> createState() => _CustomMaterialIndicatorScreenState();
11+
}
12+
13+
class _CustomMaterialIndicatorScreenState extends State<CustomMaterialIndicatorScreen> {
14+
final _controller = IndicatorController();
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return Scaffold(
19+
backgroundColor: appBackgroundColor,
20+
appBar: const ExampleAppBar(
21+
elevation: 0,
22+
),
23+
body: SafeArea(
24+
child: Container(
25+
clipBehavior: Clip.hardEdge,
26+
decoration: BoxDecoration(
27+
border: Border.all(
28+
color: const Color(0xFFE2D8D7),
29+
),
30+
),
31+
child: CustomMaterialIndicator(
32+
controller: _controller,
33+
clipBehavior: Clip.antiAlias,
34+
trigger: IndicatorTrigger.bothEdges,
35+
triggerMode: IndicatorTriggerMode.anywhere,
36+
onRefresh: () => Future.delayed(const Duration(seconds: 2)),
37+
indicatorBuilder: (context, controller) {
38+
return const Icon(
39+
Icons.ac_unit,
40+
color: appContentColor,
41+
size: 30,
42+
);
43+
},
44+
scrollableBuilder: (context, child, controller) {
45+
return child;
46+
},
47+
child: ExampleList(
48+
itemCount: 12,
49+
physics: AlwaysScrollableScrollPhysics(
50+
parent: ClampingWithOverscrollPhysics(state: _controller),
51+
),
52+
),
53+
),
54+
),
55+
),
56+
);
57+
}
58+
59+
@override
60+
void dispose() {
61+
_controller.dispose();
62+
super.dispose();
63+
}
64+
}

example/lib/screens/opacity_indicator_screen.dart

Lines changed: 0 additions & 36 deletions
This file was deleted.

example/lib/widgets/example_app_bar.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ const appContentColor = Color(0xff877162);
66
class ExampleAppBar extends StatelessWidget implements PreferredSizeWidget {
77
final String? title;
88
final List<Widget>? actions;
9+
final double elevation;
910

1011
const ExampleAppBar({
1112
super.key,
1213
this.title,
1314
this.actions,
15+
this.elevation = 1.0,
1416
});
1517

1618
@override
@@ -27,7 +29,7 @@ class ExampleAppBar extends StatelessWidget implements PreferredSizeWidget {
2729
),
2830
),
2931
actions: actions,
30-
elevation: 3,
32+
elevation: elevation,
3133
);
3234
}
3335

example/lib/widgets/example_list.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ class ExampleList extends StatelessWidget {
77
final bool countElements;
88
final bool reverse;
99
final Color backgroundColor;
10+
final ScrollPhysics physics;
1011

1112
const ExampleList({
1213
super.key,
1314
this.reverse = false,
1415
this.countElements = false,
1516
this.backgroundColor = appBackgroundColor,
1617
this.itemCount = 4,
18+
this.physics = const AlwaysScrollableScrollPhysics(
19+
parent: ClampingScrollPhysics(),
20+
),
1721
});
1822

1923
@override
@@ -31,10 +35,8 @@ class ExampleList extends StatelessWidget {
3135
],
3236
),
3337
child: ListView.separated(
38+
physics: physics,
3439
reverse: reverse,
35-
physics: const AlwaysScrollableScrollPhysics(
36-
parent: ClampingScrollPhysics(),
37-
),
3840
itemBuilder: (BuildContext context, int index) => countElements
3941
? Element(
4042
child: Center(

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ packages:
4747
path: ".."
4848
relative: true
4949
source: path
50-
version: "3.0.0"
50+
version: "3.1.0-dev.1"
5151
fake_async:
5252
dependency: transitive
5353
description:

lib/custom_refresh_indicator.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export 'src/data/data.dart';
22
export 'src/custom_refresh_indicator.dart';
33
export 'src/delegates/delegates.dart';
44
export 'src/widgets/widgets.dart';
5+
export 'src/physics/physics.dart';
56
export 'src/utils/utils.dart';

lib/src/controller.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
part of 'custom_refresh_indicator.dart';
22

33
class IndicatorController extends Animation<double>
4-
with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
4+
with
5+
AnimationEagerListenerMixin,
6+
AnimationLocalListenersMixin,
7+
AnimationLocalStatusListenersMixin,
8+
ClampingWithOverscrollPhysicsState {
59
double _value;
610

711
/// Current indicator value / progress
@@ -121,7 +125,7 @@ class IndicatorController extends Animation<double>
121125
/// will have the direction of `AxisDirection.left`
122126
AxisDirection get direction => _direction;
123127

124-
/// Whether list scrolls horrizontally
128+
/// Whether list scrolls horizontally
125129
///
126130
/// (direction equals `AxisDirection.left` or `AxisDirection.right`)
127131
bool get isHorizontalDirection => direction == AxisDirection.left || direction == AxisDirection.right;

lib/src/custom_refresh_indicator.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ class CustomRefreshIndicator extends StatefulWidget {
155155
}
156156

157157
CustomRefreshIndicator({
158-
Key? key,
158+
super.key,
159159
required this.child,
160160
required this.onRefresh,
161161
required this.builder,
@@ -189,8 +189,7 @@ class CustomRefreshIndicator extends StatefulWidget {
189189
settleDuration: indicatorSettleDuration ?? durations.settleDuration,
190190
),
191191
// set the default extent percentage value if not provided
192-
containerExtentPercentageToArmed = containerExtentPercentageToArmed ?? defaultContainerExtentPercentageToArmed,
193-
super(key: key);
192+
containerExtentPercentageToArmed = containerExtentPercentageToArmed ?? defaultContainerExtentPercentageToArmed;
194193

195194
@override
196195
CustomRefreshIndicatorState createState() => CustomRefreshIndicatorState();
@@ -388,6 +387,8 @@ class CustomRefreshIndicatorState extends State<CustomRefreshIndicator> with Tic
388387
}
389388

390389
bool _handleScrollEndNotification(ScrollEndNotification notification) {
390+
controller.clearPhysicsState();
391+
391392
if (controller.state.isArmed) {
392393
_start();
393394
} else {
@@ -578,9 +579,10 @@ class CustomRefreshIndicatorState extends State<CustomRefreshIndicator> with Tic
578579
Future<void> _hide() async {
579580
setIndicatorState(IndicatorState.canceling);
580581
_dragOffset = 0;
582+
final progress = _animationController.value;
581583
await _animationController.animateTo(
582584
0.0,
583-
duration: widget.durations.cancelDuration,
585+
duration: widget.durations.cancelDuration * progress,
584586
curve: Curves.ease,
585587
);
586588

@@ -600,7 +602,6 @@ class CustomRefreshIndicatorState extends State<CustomRefreshIndicator> with Tic
600602
);
601603

602604
final builder = widget.builder;
603-
604605
if (widget.autoRebuild ||
605606
// ignore: deprecated_member_use_from_same_package
606607
(builder is IndicatorBuilderDelegate && (builder as IndicatorBuilderDelegate).autoRebuild)) {

0 commit comments

Comments
 (0)