Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get index of the SliverList child when it's scrolled out using ScrollNotification

Tags:

flutter

I have this application that has a sliverlist and a sliverAppbar. I need to get the current scroll position of each item in the sliverlist onscroll and determine if it has crossed the sliverAppbar and update the sliverappbar with the Title of the item. Say starting from Item1 once it crosses the SliverAppBar that means I am viewing Item1 content and when Item2 crosses the SliverAppBar, update the SliverAppBar with the title Item2 to mean the user is viewing Item2 content

I am trying to implement this using a NotificationListener<ScrollEndNotification> but I am stuck at the second NotificationListener that is supposed to emit notifications to the top of the parent at this line ScrollEndNotification(metrics: , context:context).dispatch(context); it throws an error that I should provide a metrics parameter which I don't know what to provide.

            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  ScrollEndNotification(metrics: , context:context).dispatch(context);
                  return  AutoScrollTag(
                        key: ValueKey(index),
                        controller: controller,
                        index: index,
                        child: Padding(
                     padding: const EdgeInsets.only(
                      top: 30.0, left: 20.0, right: 20.0),
                      child: Container(
                       color: Colors.red,
                     //height: 120.0
                     //height: A varying height

                        ),),},); ),

The complete code is

           Widget build(BuildContext context) {
           return Scaffold(
           backgroundColor: Colors.grey[100],
           body: NotificationListener<ScrollEndNotification>(
                    onNotification: (notification) {
                      if (notification is ScrollEndNotification) {
                     ///Here I need to know what widget index bubbled the notification, its position 
                     ///on the screen and its index 
                     //in the list, in order to do further implementation like update the 
                      //SliverAppBar
                      print('$notification');          
                      return true;
                    },
              child: CustomScrollView(
                 controller: controller
             slivers: <Widget>[
              SliverAppBar(
            title: Text(title),
             ), 
         SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  ScrollEndNotification(metrics: , context:context).dispatch(context);
                  return  AutoScrollTag(
                        key: ValueKey(index),
                        controller: controller,
                        index: index,
                        child: Padding(
                     padding: const EdgeInsets.only(
                      top: 30.0, left: 20.0, right: 20.0),
                      child: Container(
                       color: Colors.red,
                     //height: 120.0
                     //height: A varying height
                        ),),},); ),

Also if you have a better implementation on how I can achieve this do help me out. In short, I need to keep track when an item is scrolled off the screen and get the say index of it in the Sliverlist. Keeping in mind that the item has a variable size container that expands according to the number of children in it. It is a common UX pattern in ecommerce apps. Eg Viewing menus as the user scrolls down and updating what menu the user is viewing as the title crosses the screen.

Providing a link to the gist so you get an idea of the complete implementation

like image 937
Taio Avatar asked Oct 27 '22 12:10

Taio


1 Answers

I implemented what you want for various height children.
To make a various child's height, give a height randomly.

  1. Get each index children's height and save distance from top.
  2. Add a scroll listener for get current scroll position
  3. Get just hidden child index with n'th child's distance and current position
  4. Change the title with just hidden child index

enter image description here

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:scroll_to_index/scroll_to_index.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String title = 'Viewing item 0';
  Random random = Random();

  Map<int, double> itemHeight = {};
  int currentHideIndex = 0;

  final _mainScrollController = ScrollController();
  @override
  void initState() {
    super.initState();
    _mainScrollController.addListener(_onMainScroll);
  }

  void _onMainScroll() {
    int justHiddenIndex =
        findIndexJustHidden(_mainScrollController.position.pixels);
    print('justHiddenIndex: $justHiddenIndex');
    if (currentHideIndex != justHiddenIndex) {
      setState(() {
        title = 'Viewing item ${justHiddenIndex + 1}';
      });
      currentHideIndex = justHiddenIndex;
    }
  }

  int findIndexJustHidden(currentPosition) {
    int index = -1;
    for (var item in itemHeight.entries) {
      if (currentPosition > item.value) {
        index = item.key;
      } else {
        if (index != 0) {
          return index;
        }
      }
    }
    return index;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      body: CustomScrollView(
        controller: _mainScrollController,
        slivers: <Widget>[
          SliverAppBar(
            title: Text(title),
            pinned: true,
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                double randomHeight;
                if (!itemHeight.containsKey(index)) {
                  randomHeight = (random.nextInt(100) + 40) * 1.0;
                  print('index: $index, randomHeight: $randomHeight');
                  double beforeSumHeight =
                      index == 0 ? 0 : itemHeight[index - 1];
                  print({
                    'index': index,
                    'beforeSumHeight': beforeSumHeight,
                    'height': randomHeight
                  });
                  itemHeight[index] = beforeSumHeight + randomHeight;
                } else {
                  randomHeight = index == 0
                      ? itemHeight[index]
                      : itemHeight[index] - itemHeight[index - 1];
                }

                return AutoScrollTag(
                  key: ValueKey(index),
                  controller: AutoScrollController(),
                  index: index,
                  child: Container(
                    height: randomHeight,
                    decoration: BoxDecoration(
                        color: Colors.red.withOpacity(0.5),
                        border: Border(
                          bottom: BorderSide(
                            color: Color(0XFF000000).withOpacity(0.08),
                            width: 1.0,
                            style: BorderStyle.solid,
                          ),
                        )),
                    padding: EdgeInsets.only(top: 10, left: 20.0, right: 20.0),
                    child: Text('$index'),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildBody() {
    return Container();
  }
}

class WidgetSize extends StatefulWidget {
  final Widget child;
  final Function onChange;

  const WidgetSize({
    Key key,
    @required this.onChange,
    @required this.child,
  }) : super(key: key);

  @override
  _WidgetSizeState createState() => _WidgetSizeState();
}

class _WidgetSizeState extends State<WidgetSize> {
  @override
  Widget build(BuildContext context) {
    SchedulerBinding.instance.addPostFrameCallback(postFrameCallback);
    return Container(
      key: widgetKey,
      child: widget.child,
    );
  }

  var widgetKey = GlobalKey();
  var oldSize;

  void postFrameCallback(_) {
    var context = widgetKey.currentContext;
    if (context == null) return;

    var newSize = context.size;
    if (oldSize == newSize) return;

    oldSize = newSize;
    widget.onChange(newSize);
  }
}

like image 163
KuKu Avatar answered Jan 02 '23 19:01

KuKu