0

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()),
              );
            }
          },
        ),
      ),
    );
  }
}
  • I think the main problem is in this if condition ( if (minExtent < pos && pos <= middle) ) and the else if condition ( else if (middle < pos && pos < maxExtent) ) which is in the _scrollNotificationHandler function of custom_nested_scroll_view.dart. Can anyone help me? – Bhuvana Chandra Jul 15 '23 at 12:54

0 Answers0