Created
July 15, 2024 22:04
-
-
Save callmephil/1a8333636dc59ef88882dc51f6858bb8 to your computer and use it in GitHub Desktop.
smooth page view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/gestures.dart'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
class SmoothPageView extends StatefulWidget { | |
const SmoothPageView({ | |
super.key, | |
required this.onPageChanged, | |
required this.pages, | |
required this.pageIndex, | |
}); | |
final ValueNotifier<int> pageIndex; | |
final void Function(int index) onPageChanged; | |
final List<Widget> pages; | |
@override | |
State<SmoothPageView> createState() => _SmoothPageViewState(); | |
} | |
class _SmoothPageViewState extends State<SmoothPageView> { | |
late final PageController _pageController; | |
final FocusNode _focusNode = FocusNode(); | |
bool _canPhysicScroll = true; | |
@override | |
void initState() { | |
super.initState(); | |
_pageController = PageController(initialPage: widget.pageIndex.value) | |
..addListener(_onPageChanged); | |
widget.pageIndex.addListener(_onPageIndexChanged); | |
WidgetsBinding.instance.addPostFrameCallback((_) { | |
_focusNode.requestFocus(); | |
}); | |
} | |
@override | |
void dispose() { | |
_focusNode.dispose(); | |
_pageController.removeListener(_onPageChanged); | |
_pageController.dispose(); | |
widget.pageIndex.removeListener(_onPageIndexChanged); | |
super.dispose(); | |
} | |
void setSafeState(void Function() fn) { | |
if (!mounted) return; | |
setState(fn); | |
} | |
void _onPageIndexChanged() { | |
if (_pageController.page == widget.pageIndex.value) return; | |
if (_pageController.position.isScrollingNotifier.value) return; | |
_pageController.animateToPage( | |
widget.pageIndex.value, | |
duration: const Duration(milliseconds: 300), | |
curve: Curves.easeInOut, | |
); | |
} | |
void _onPageChanged() { | |
// if (isInit) { | |
// isInit = false; | |
// } | |
// setSafeState(() {}); | |
widget.onPageChanged(_pageController.page?.floor().abs() ?? 0); | |
} | |
int get _pageCount => widget.pages.length; | |
bool get _isScrolling => _pageController.position.isScrollingNotifier.value; | |
void _onPreviousPage() { | |
if (_isScrolling) return; | |
_pageController.previousPage( | |
duration: const Duration(milliseconds: 300), | |
curve: Curves.easeInOut, | |
); | |
} | |
void _onNextPage() { | |
if (_isScrolling) return; | |
_pageController.nextPage( | |
duration: const Duration(milliseconds: 300), | |
curve: Curves.easeInOut, | |
); | |
} | |
void _onPointerDown(PointerDownEvent event) { | |
if (event.position.dx > 10) return; | |
if (event.kind != PointerDeviceKind.mouse) return; | |
_setPhysicalScroll(true); | |
} | |
void _setPhysicalScroll(bool value) { | |
if (_canPhysicScroll == value) return; | |
setSafeState(() { | |
_canPhysicScroll = value; | |
}); | |
// TODO: add a debounce timer to reset the canPhysicCall | |
} | |
void _onPointerSignal(PointerSignalEvent pointerSignal) { | |
if (pointerSignal is! PointerScrollEvent) return; | |
if (pointerSignal.kind == PointerDeviceKind.mouse) { | |
_setPhysicalScroll(false); | |
} else { | |
_setPhysicalScroll(true); | |
return; | |
} | |
if (_isScrolling) return; | |
// Check if the scroll is downward | |
if (pointerSignal.scrollDelta.dy > 0) { | |
// Scroll is going down, navigate to the next page | |
// Assuming itemCount is 3 | |
if (_pageController.page == _pageCount) return; | |
_onNextPage(); | |
} else { | |
if (_pageController.page == 0) return; | |
_onPreviousPage(); | |
} | |
} | |
@override | |
Widget build(BuildContext context) { | |
return KeyboardListener( | |
focusNode: _focusNode, | |
onKeyEvent: (event) { | |
if (event is! KeyDownEvent) return; | |
switch (event.logicalKey) { | |
case LogicalKeyboardKey.arrowUp: | |
_onPreviousPage(); | |
break; | |
case LogicalKeyboardKey.arrowDown: | |
_onNextPage(); | |
break; | |
} | |
}, | |
child: Stack( | |
fit: StackFit.expand, | |
children: [ | |
Listener( | |
onPointerDown: _onPointerDown, | |
onPointerSignal: _onPointerSignal, | |
child: PageView.builder( | |
itemCount: _pageCount, | |
scrollDirection: Axis.vertical, | |
controller: _pageController, | |
scrollBehavior: const MaterialScrollBehavior(), | |
physics: _canPhysicScroll | |
? const AlwaysScrollableScrollPhysics() | |
: const NeverScrollableScrollPhysics(), | |
itemBuilder: (_, int index) { | |
return widget.pages[index]; | |
}, | |
), | |
), | |
], | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment