I'm using CustomSliverDelegate
and CustomNestedScrollView
. When i scroll up from the bottom of the screen, the scroll behaviour is working perfectly fine.After scrolling up, i tried to scroll down from the middle of the screen or just above from the bottom of the screen, the SliverGrid
is automatically scrolling all the way to the top of the SliverGrid
. I want to remove this behaviour. But when i scroll down from the bottom part of the screen, the scroll behaviour is working perfectly fine. I'm testing this on real device.
Here's the scroll behaviour video
right click on the video and open in new tab for better view.
Take a look at this GIF (Telegram gif)
That is the scroll behaviour that i want to achieve.
code is below.
custom_nested_scroll_view.dart
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../widgets/common/custom_sliver_delegate.dart';
class CustomNestedScrollView extends StatelessWidget {
final Widget widget;
final String appbarName;
final Widget sliverCard;
CustomNestedScrollView({
super.key,
required this.widget,
required this.appbarName,
required this.sliverCard,
});
final _controller = ScrollController();
bool _scrollNotificationHandler(scrollNotification) {
if (scrollNotification is ScrollEndNotification &&
scrollNotification.depth == 0) {
final minExtent = scrollNotification.metrics.minScrollExtent;
final maxExtent = scrollNotification.metrics.maxScrollExtent;
final middle = (maxExtent - minExtent) / 2;
final pos = scrollNotification.metrics.pixels;
double? scrollTo;
if (minExtent < pos && pos <= middle) {
scrollTo = minExtent;
} else if (middle < pos && pos < maxExtent) {
scrollTo = maxExtent;
} else {
scrollTo = null;
}
if (scrollTo != null) {
Timer(
const Duration(milliseconds: 1),
() => _controller.animateTo(scrollTo ?? 0.0,
duration: const Duration(milliseconds: 300), curve: Curves.ease),
);
}
}
return true;
}
@override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: _scrollNotificationHandler,
child: NestedScrollView(
controller: _controller,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverPersistentHeader(
pinned: true,
delegate: CustomSliverDelegate(
sliverCard: sliverCard,
appbarName: appbarName,
expandedHeight: MediaQuery.of(context).size.height / 4.5,
),
),
),
];
},
body: SafeArea(
top: false,
child: Builder(builder: (BuildContext context) {
return CustomScrollView(
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context,
),
),
// const SliverToBoxAdapter(
// child: SizedBox(
// height: 10,
// ),
// ),
widget,
],
);
}),
),
),
);
}
}
custom_sliver_delegate.dart
import 'package:flutter/material.dart';
import '../../utils/colors.dart';
class CustomSliverDelegate extends SliverPersistentHeaderDelegate {
final String appbarName;
final Widget sliverCard;
final double expandedHeight;
final bool hideTitleWhenExpanded;
CustomSliverDelegate({
required this.expandedHeight,
required this.appbarName,
required this.sliverCard,
this.hideTitleWhenExpanded = true,
});
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final appBarSize = expandedHeight - shrinkOffset;
final cardTopPosition = expandedHeight / 2 - shrinkOffset;
final proportion = 2 - (expandedHeight / appBarSize);
final percent = proportion < 0 || proportion > 1 ? 0.0 : proportion;
return SizedBox(
height: expandedHeight + expandedHeight / 1,
child: Stack(
children: [
SizedBox(
height: appBarSize < kToolbarHeight
? kToolbarHeight + 40
: appBarSize + 40,
child: AppBar(
iconTheme: IconThemeData(
color: cardTopPosition <= 0 ? Colors.black : Colors.white,
),
backgroundColor:
cardTopPosition <= 0 ? Colors.white : AppColors.primaryColor,
elevation: 0.0,
title: Opacity(
opacity: hideTitleWhenExpanded ? 1.0 - percent : 1.0,
child: Text(
appbarName,
),
),
),
),
cardTopPosition <= 0
? const SizedBox.shrink()
: Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
height: 150,
width: double.infinity,
child: Opacity(
opacity: percent,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8 * percent),
child: Card(
surfaceTintColor: Colors.white,
child: Padding(
padding: const EdgeInsets.all(16),
child: sliverCard,
),
),
),
),
),
),
],
),
);
}
@override
double get maxExtent => expandedHeight + expandedHeight / 2;
@override
double get minExtent => 100;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}
grid_view_screen.dart
class GridViewScreen extends StatefulWidget {
const GridViewScreen({super.key});
@override
State<GridViewScreen> createState() =>
_GridViewScreenState();
}
class _GridViewScreenState extends State<GridViewScreen> {
@override
void initState() {
context.read<GridDataBloc>().add(GridDataLoadEvent());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => context.push("/add"),
child: const Icon(Icons.add),
),
body: CustomNestedScrollView(
sliverCard: const AppBarCard(),
appbarName: "Product categories",
widget: BlocConsumer<GridDataBloc, GridDataBlocState>(
listener: (context, state) {
if (state is GridDataErrorState) {
showSnackBar("Something went wrong! please try again.");
}
},
builder: (context, state) {
if (state is GridDataLoadingState) {
return const SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()),
);
} else if (state is GridDataLoadedState) {
final categories = state.productCategories;
return GridDataCards(categories: categories); // SliverGrid Widget
} else {
return const SliverToBoxAdapter(
child: Center(child: CircularProgressIndicator()),
);
}
},
),
),
);
}
}