fix: improve scroll handling on home page to avoid unintentional scroll event detection

This commit is contained in:
Michael Thomas 2023-02-09 20:00:05 -05:00
parent 67efdc0289
commit aed14e8159
5 changed files with 128 additions and 64 deletions

View File

@ -8,9 +8,11 @@ import 'package:furman_now/src/widgets/scroll_view_height.dart';
class HomeContent extends StatefulWidget { class HomeContent extends StatefulWidget {
final bool collapse; final bool collapse;
final ScrollController controller;
const HomeContent({ const HomeContent({
required this.collapse, required this.collapse,
required this.controller,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -19,13 +21,12 @@ class HomeContent extends StatefulWidget {
} }
class _HomeContentState extends State<HomeContent> { class _HomeContentState extends State<HomeContent> {
final ScrollController _controller = ScrollController();
bool _collapse = false; bool _collapse = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.collapse != _collapse) { if (widget.collapse != _collapse) {
_controller.animateTo( widget.controller.animateTo(
0, 0,
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -35,7 +36,7 @@ class _HomeContentState extends State<HomeContent> {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) => ScrollViewWithHeight( builder: (context, constraints) => ScrollViewWithHeight(
controller: _controller, controller: widget.controller,
child: Align( child: Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: TweenAnimationBuilder<double>( child: TweenAnimationBuilder<double>(
@ -50,7 +51,10 @@ class _HomeContentState extends State<HomeContent> {
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: borderRadius:
BorderRadius.vertical(top: Radius.circular(30)), BorderRadius.vertical(
top: Radius.circular(30),
bottom: Radius.circular(30),
),
), ),
padding: const EdgeInsets.only(bottom: 15), padding: const EdgeInsets.only(bottom: 15),
margin: EdgeInsets.only(top: margin), margin: EdgeInsets.only(top: margin),

View File

@ -44,6 +44,7 @@ class HomePageHeader extends StatelessWidget {
color: Color(0xff26183d), color: Color(0xff26183d),
fontSize: 36, fontSize: 36,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
height: 1.2,
)), )),
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:furman_now/src/routes/index.gr.dart'; import 'package:furman_now/src/routes/index.gr.dart';
import 'package:furman_now/src/screens/home/content.dart'; import 'package:furman_now/src/screens/home/content.dart';
import 'package:furman_now/src/screens/home/state.dart'; import 'package:furman_now/src/screens/home/state.dart';
@ -13,75 +14,124 @@ class HomeScreen extends StatefulWidget {
} }
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
bool _collapse = false; final ScrollController _controller = ScrollController();
double overscrollTopAmount = 0;
double overscrollBottomAmount = 0;
@override
void initState() {
super.initState();
_controller.addListener(updateScrollPosition);
}
updateScrollPosition() {
// bottom overscroll
if (_controller.position.pixels > _controller.position.maxScrollExtent) {
setState(() {
overscrollBottomAmount =
_controller.position.pixels - _controller.position.maxScrollExtent;
});
} else {
if (overscrollBottomAmount != 0) {
setState(() {
overscrollBottomAmount = 0;
});
}
}
// top overscroll
if (_controller.position.pixels < 0) {
setState(() {
overscrollTopAmount =
_controller.position.pixels.abs();
});
} else {
if (overscrollTopAmount != 0) {
setState(() {
overscrollTopAmount = 0;
});
}
}
}
handleScrollEvent(BuildContext context) {
const scrollMessageSensitivity = 20;
const pageSwitchSensitivity = 60;
var offsetAmount = overscrollTopAmount.abs();
if (overscrollTopAmount != 0) {
if (offsetAmount > pageSwitchSensitivity) {
context.router.navigate(const StudentIdRoute());
context.read<HomePageState>().collapse = true;
} else if (offsetAmount > scrollMessageSensitivity) {
context.read<HomePageState>().showScrollMessage = true;
} else {
context.read<HomePageState>().showScrollMessage = false;
}
} else {
context.read<HomePageState>().showScrollMessage = false;
if (_controller.position.pixels > pageSwitchSensitivity) {
context.router.navigate(const HomeRoute());
context.read<HomePageState>().collapse = false;
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider( return ChangeNotifierProvider(
create: (context) => HomePageState(), create: (context) => HomePageState(),
builder: (context, _) => Scaffold( builder: (context, _) => Scaffold(
body: Container( body: AnnotatedRegion<SystemUiOverlayStyle>(
color: const Color(0xffb7acc9), value: SystemUiOverlayStyle.light,
child: SafeArea(
child: Container( child: Container(
color: const Color(0xffb7acc9), color: const Color(0xffb7acc9),
child: Stack( child: SafeArea(
fit: StackFit.loose, child: Container(
children: [ color: const Color(0xffb7acc9),
const AutoTabsRouter( child: Stack(
routes: [ fit: StackFit.loose,
HomeRoute(), children: [
StudentIdRoute(), // overscroll indicator color
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: overscrollBottomAmount + 30,
color: Colors.grey.shade50,
),
),
const AutoTabsRouter(
routes: [
HomeRoute(),
StudentIdRoute(),
],
),
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
handleScrollEvent(context);
}
return true;
},
child: Consumer<HomePageState>(
builder: (context, state, _) =>
ClipRect(child: HomeContent(
controller: _controller,
collapse: state.collapse,
),
),
),
),
], ],
), ),
NotificationListener<ScrollNotification>( ),
onNotification: (notification) {
if (notification is ScrollUpdateNotification) {
final offset = notification.metrics.pixels;
const scrollMessageSensitivity = 20;
const pageSwitchSensitivity = 60;
if (offset < 0) {
var offsetAmount = offset.abs();
if (offsetAmount > pageSwitchSensitivity) {
context.router.navigate(const StudentIdRoute());
setState(() {
_collapse = true;
});
} else if (offsetAmount > scrollMessageSensitivity) {
if (!context.read<HomePageState>().showScrollMessage) {
context.read<HomePageState>().showScrollMessage = true;
}
} else {
if (context.read<HomePageState>().showScrollMessage) {
context.read<HomePageState>().showScrollMessage = false;
}
}
} else {
var offsetAmount = offset.abs();
if (offsetAmount > pageSwitchSensitivity) {
context.router.navigate(const HomeRoute());
setState(() {
_collapse = false;
});
} else {
if (context.read<HomePageState>().showScrollMessage) {
context.read<HomePageState>().showScrollMessage = false;
}
}
}
}
return true;
},
child: ClipRect(child: HomeContent(
collapse: _collapse,
)),
),
],
), ),
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -2,13 +2,21 @@ import 'package:flutter/material.dart';
class HomePageState extends ChangeNotifier { class HomePageState extends ChangeNotifier {
bool _showScrollMessage = false; bool _showScrollMessage = false;
bool _collapse = false;
bool get showScrollMessage { bool get showScrollMessage { return _showScrollMessage; }
return _showScrollMessage; set showScrollMessage(bool value) {
if (value != _showScrollMessage) {
_showScrollMessage = value;
notifyListeners();
}
} }
set showScrollMessage(bool value) { bool get collapse { return _collapse; }
_showScrollMessage = value; set collapse(bool value) {
notifyListeners(); if (value != _collapse) {
_collapse = value;
notifyListeners();
}
} }
} }

View File

@ -29,6 +29,7 @@ class _RestaurantsListState extends State<RestaurantsList> {
itemCount: restaurants.length, itemCount: restaurants.length,
cacheExtent: 10000, cacheExtent: 10000,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
prototypeItem: Padding( prototypeItem: Padding(
padding: const EdgeInsets.only(right: 15), padding: const EdgeInsets.only(right: 15),
child: RestaurantCard(restaurant: restaurants.first), child: RestaurantCard(restaurant: restaurants.first),