From d7f334663288c3090388d9b2a83d41aefc0cdac3 Mon Sep 17 00:00:00 2001
From: "n.morozov"
Date: Wed, 20 Apr 2022 18:28:38 +0300
Subject: [PATCH 1/4] speed up scrolling by mouse wheel
---
.../lib/src/scrollable_positioned_list.dart | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
index 60045e9b..c03fd424 100644
--- a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
+++ b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart' show IterableExtension;
+import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@@ -57,6 +58,7 @@ class ScrollablePositionedList extends StatefulWidget {
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.minCacheExtent,
+ this.extraScrollSpeed,
}) : assert(itemCount != null),
assert(itemBuilder != null),
itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?,
@@ -87,12 +89,14 @@ class ScrollablePositionedList extends StatefulWidget {
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.minCacheExtent,
+ this.extraScrollSpeed,
}) : assert(itemCount != null),
assert(itemBuilder != null),
assert(separatorBuilder != null),
itemPositionsNotifier = itemPositionsListener as ItemPositionsNotifier?,
scrollOffsetNotifier = scrollOffsetListener as ScrollOffsetNotifier?,
super(key: key);
+ final int? extraScrollSpeed;
/// Number of items the [itemBuilder] can produce.
final int itemCount;
@@ -324,6 +328,30 @@ class _ScrollablePositionedListState extends State
double previousOffset = 0;
+ void _speedUpScrollListener(ScrollController controller) {
+ if (widget.extraScrollSpeed == null || widget.extraScrollSpeed == 0) {
+ return;
+ }
+ // if (!controller.hasClients) {
+ // return;
+ // }
+ ScrollDirection scrollDirection = controller.position.userScrollDirection;
+ if (scrollDirection != ScrollDirection.idle) {
+ double scrollEnd = controller.offset +
+ (scrollDirection == ScrollDirection.reverse
+ ? widget.extraScrollSpeed!
+ : -widget.extraScrollSpeed!);
+ scrollEnd = min(controller.position.maxScrollExtent,
+ max(controller.position.minScrollExtent, scrollEnd));
+ controller.jumpTo(scrollEnd);
+ }
+ }
+
+ void _speedupClosurePrimary() =>
+ _speedUpScrollListener(primary.scrollController);
+ void _speedupClosureSecondary() =>
+ _speedUpScrollListener(secondary.scrollController);
+
@override
void initState() {
super.initState();
@@ -338,6 +366,8 @@ class _ScrollablePositionedListState extends State
widget.scrollOffsetController?._attach(this);
primary.itemPositionsNotifier.itemPositions.addListener(_updatePositions);
secondary.itemPositionsNotifier.itemPositions.addListener(_updatePositions);
+ primary.scrollController.addListener(_speedupClosurePrimary);
+ secondary.scrollController.addListener(_speedupClosureSecondary);
primary.scrollController.addListener(() {
final currentOffset = primary.scrollController.offset;
final offsetChange = currentOffset - previousOffset;
@@ -369,6 +399,8 @@ class _ScrollablePositionedListState extends State
.removeListener(_updatePositions);
secondary.itemPositionsNotifier.itemPositions
.removeListener(_updatePositions);
+ primary.scrollController.removeListener(_speedupClosurePrimary);
+ secondary.scrollController.removeListener(_speedupClosureSecondary);
_animationController?.dispose();
super.dispose();
}
@@ -612,9 +644,13 @@ class _ScrollablePositionedListState extends State
if (opacity.value >= 0.5) {
// Secondary [ListView] is more visible than the primary; make it the
// new primary.
+ primary.scrollController.removeListener(_speedupClosurePrimary);
var temp = primary;
+ secondary.scrollController.removeListener(_speedupClosureSecondary);
primary = secondary;
+ primary.scrollController.addListener(_speedupClosurePrimary);
secondary = temp;
+ secondary.scrollController.removeListener(_speedupClosureSecondary);
}
_isTransitioning = false;
opacity.parent = const AlwaysStoppedAnimation(0);
From dea3b03810374159999f1c16ad174ac15d4a9d47 Mon Sep 17 00:00:00 2001
From: "n.morozov"
Date: Mon, 22 Aug 2022 15:38:23 +0300
Subject: [PATCH 2/4] scoll acceleration now wouldn't conflict with touchscreen
gestures
---
.../lib/src/scrollable_positioned_list.dart | 100 ++++++++++--------
1 file changed, 55 insertions(+), 45 deletions(-)
diff --git a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
index c03fd424..7a05a710 100644
--- a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
+++ b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart' show IterableExtension;
+import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
@@ -323,18 +324,18 @@ class _ScrollablePositionedListState extends State
void Function() startAnimationCallback = () {};
bool _isTransitioning = false;
+ bool _isTouchScreen = false;
var _animationController;
double previousOffset = 0;
void _speedUpScrollListener(ScrollController controller) {
- if (widget.extraScrollSpeed == null || widget.extraScrollSpeed == 0) {
+ if (widget.extraScrollSpeed == null ||
+ widget.extraScrollSpeed == 0 ||
+ _isTouchScreen) {
return;
}
- // if (!controller.hasClients) {
- // return;
- // }
ScrollDirection scrollDirection = controller.position.userScrollDirection;
if (scrollDirection != ScrollDirection.idle) {
double scrollEnd = controller.offset +
@@ -441,57 +442,34 @@ class _ScrollablePositionedListState extends State
builder: (context, constraints) {
final cacheExtent = _cacheExtent(constraints);
return Listener(
- onPointerDown: (_) => _stopScroll(canceled: true),
- child: Stack(
- children: [
- PostMountCallback(
- key: primary.key,
- callback: startAnimationCallback,
- child: FadeTransition(
- opacity: ReverseAnimation(opacity),
- child: NotificationListener(
- onNotification: (_) => _isTransitioning,
- child: PositionedList(
- itemBuilder: widget.itemBuilder,
- separatorBuilder: widget.separatorBuilder,
- itemCount: widget.itemCount,
- positionedIndex: primary.target,
- controller: primary.scrollController,
- itemPositionsNotifier: primary.itemPositionsNotifier,
- scrollDirection: widget.scrollDirection,
- reverse: widget.reverse,
- cacheExtent: cacheExtent,
- alignment: primary.alignment,
- physics: widget.physics,
- shrinkWrap: widget.shrinkWrap,
- addSemanticIndexes: widget.addSemanticIndexes,
- semanticChildCount: widget.semanticChildCount,
- padding: widget.padding,
- addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
- addRepaintBoundaries: widget.addRepaintBoundaries,
- ),
- ),
- ),
- ),
- if (_isTransitioning)
+ onPointerMove: (event) {
+ _isTouchScreen = event.kind == PointerDeviceKind.touch;
+ },
+ onPointerHover: (event) =>
+ _isTouchScreen = event.kind == PointerDeviceKind.touch,
+ child: GestureDetector(
+ onPanDown: (_) => _stopScroll(canceled: true),
+ excludeFromSemantics: true,
+ child: Stack(
+ children: [
PostMountCallback(
- key: secondary.key,
+ key: primary.key,
callback: startAnimationCallback,
child: FadeTransition(
- opacity: opacity,
+ opacity: ReverseAnimation(opacity),
child: NotificationListener(
- onNotification: (_) => false,
+ onNotification: (_) => _isTransitioning,
child: PositionedList(
itemBuilder: widget.itemBuilder,
separatorBuilder: widget.separatorBuilder,
itemCount: widget.itemCount,
- itemPositionsNotifier: secondary.itemPositionsNotifier,
- positionedIndex: secondary.target,
- controller: secondary.scrollController,
+ positionedIndex: primary.target,
+ controller: primary.scrollController,
+ itemPositionsNotifier: primary.itemPositionsNotifier,
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
cacheExtent: cacheExtent,
- alignment: secondary.alignment,
+ alignment: primary.alignment,
physics: widget.physics,
shrinkWrap: widget.shrinkWrap,
addSemanticIndexes: widget.addSemanticIndexes,
@@ -503,7 +481,39 @@ class _ScrollablePositionedListState extends State
),
),
),
- ],
+ if (_isTransitioning)
+ PostMountCallback(
+ key: secondary.key,
+ callback: startAnimationCallback,
+ child: FadeTransition(
+ opacity: opacity,
+ child: NotificationListener(
+ onNotification: (_) => false,
+ child: PositionedList(
+ itemBuilder: widget.itemBuilder,
+ separatorBuilder: widget.separatorBuilder,
+ itemCount: widget.itemCount,
+ itemPositionsNotifier:
+ secondary.itemPositionsNotifier,
+ positionedIndex: secondary.target,
+ controller: secondary.scrollController,
+ scrollDirection: widget.scrollDirection,
+ reverse: widget.reverse,
+ cacheExtent: cacheExtent,
+ alignment: secondary.alignment,
+ physics: widget.physics,
+ shrinkWrap: widget.shrinkWrap,
+ addSemanticIndexes: widget.addSemanticIndexes,
+ semanticChildCount: widget.semanticChildCount,
+ padding: widget.padding,
+ addAutomaticKeepAlives: widget.addAutomaticKeepAlives,
+ addRepaintBoundaries: widget.addRepaintBoundaries,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
),
);
},
From 93b3644d8fb79dc871afd42c11e3d992be1ec6ae Mon Sep 17 00:00:00 2001
From: "n.morozov"
Date: Mon, 19 Sep 2022 16:40:57 +0300
Subject: [PATCH 3/4] update for flutter 3.3.*: touchpad scrolling became
kinetic and flutter now detects it as "Trackpad" device kind
---
.../lib/src/scrollable_positioned_list.dart | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
index 7a05a710..a597fad1 100644
--- a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
+++ b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
@@ -443,10 +443,11 @@ class _ScrollablePositionedListState extends State
final cacheExtent = _cacheExtent(constraints);
return Listener(
onPointerMove: (event) {
- _isTouchScreen = event.kind == PointerDeviceKind.touch;
+ _isTouchScreen = event.kind == PointerDeviceKind.touch || event.kind == PointerDeviceKind.trackpad;
+ },
+ onPointerHover: (event) {
+ _isTouchScreen = event.kind == PointerDeviceKind.touch || event.kind == PointerDeviceKind.trackpad;
},
- onPointerHover: (event) =>
- _isTouchScreen = event.kind == PointerDeviceKind.touch,
child: GestureDetector(
onPanDown: (_) => _stopScroll(canceled: true),
excludeFromSemantics: true,
From fca251aa7501121d529df2bcdcb6b61053bd9d68 Mon Sep 17 00:00:00 2001
From: "n.morozov"
Date: Wed, 26 Oct 2022 10:34:21 +0300
Subject: [PATCH 4/4] Optimization of switching between different scroll
devices: touchscreen/touchpad/mouse wheel. From Flutter 3.3.0 there is new
Listener's event usage (see
https://github.com/flutter/flutter/issues/112880#issuecomment-1270504869)
---
.../lib/src/scrollable_positioned_list.dart | 22 +++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
index a597fad1..d24e71ef 100644
--- a/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
+++ b/packages/scrollable_positioned_list/lib/src/scrollable_positioned_list.dart
@@ -442,11 +442,29 @@ class _ScrollablePositionedListState extends State
builder: (context, constraints) {
final cacheExtent = _cacheExtent(constraints);
return Listener(
+ onPointerDown: (event) {
+ // here we're checking if it's tap by touchscreen
+ _isTouchScreen = event.kind == PointerDeviceKind.touch ||
+ event.kind == PointerDeviceKind.trackpad;
+ },
onPointerMove: (event) {
- _isTouchScreen = event.kind == PointerDeviceKind.touch || event.kind == PointerDeviceKind.trackpad;
+ // onPointerMove triggers when finger are dragging to scroll
+ _isTouchScreen = event.kind == PointerDeviceKind.touch ||
+ event.kind == PointerDeviceKind.trackpad;
},
onPointerHover: (event) {
- _isTouchScreen = event.kind == PointerDeviceKind.touch || event.kind == PointerDeviceKind.trackpad;
+ _isTouchScreen = event.kind == PointerDeviceKind.touch ||
+ event.kind == PointerDeviceKind.trackpad;
+ },
+ onPointerPanZoomStart: (event) {
+ // onPointerPanZoomStart triggers when scrolling by touchpad
+ _isTouchScreen = event.kind == PointerDeviceKind.touch ||
+ event.kind == PointerDeviceKind.trackpad;
+ },
+ onPointerSignal: (event) {
+ if (event is PointerScrollEvent) {
+ _isTouchScreen = event.kind != PointerDeviceKind.mouse;
+ }
},
child: GestureDetector(
onPanDown: (_) => _stopScroll(canceled: true),