【问题标题】:Get index of the SliverList child when it's scrolled out using ScrollNotification使用 ScrollNotification 滚动出 SliverList 子项时获取其索引
【发布时间】:2021-07-21 04:41:15
【问题描述】:

我有这个应用程序,它有一个 sliverlist 和一个 sliverAppbar。我需要获取 sliverlist onscroll 中每个项目的当前滚动位置,并确定它是否已经越过 sliverAppbar 并使用项目的标题更新 sliverappbar。说从 Item1 开始,一旦它穿过 SliverAppBar,这意味着我正在查看 Item1 内容,当 Item2 穿过 SliverAppBar 时,使用标题 Item2 更新 SliverAppBar 以表示用户正在查看 Item2 内容

我正在尝试使用 NotificationListener<ScrollEndNotification> 来实现这一点,但我被困在第二个 NotificationListener 上,它应该向父级顶部发出通知 在这一行 ScrollEndNotification(metrics: , context:context).dispatch(context); 它会抛出一个错误,我应该提供一个 metrics 参数,我不知道该提供什么。

            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

                        ),),},); ),

完整的代码是

           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
                        ),),},); ),

此外,如果您对如何实现这一目标有更好的实施,请帮助我。简而言之,我需要跟踪某个项目何时从屏幕上滚出,并在 Sliverlist 中找到它的 index。请记住,item 有一个可变大小的容器,可根据其中的子项数量进行扩展。这是电子商务应用程序中常见的 UX 模式。例如,在用户向下滚动时查看菜单,并在标题穿过屏幕时更新用户正在查看的菜单。

为要点提供link,以便您了解完整的实现

【问题讨论】:

标签: flutter


【解决方案1】:

我为各种身高的孩子实现了你想要的。
为了制作不同的孩子身高,我使用索引值调整顶部填充。
所以第n个孩子的身高变大了。

  1. 获取每个索引儿童的身高并保存与顶部的距离。
  2. 添加滚动监听器以获取当前滚动位置
  3. 仅获取具有第 n 个孩子的距离和当前位置的隐藏子索引
  4. 仅使用隐藏的子索引更改标题

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);
  }
}

【讨论】:

  • 这看起来像孩子们逐渐增加身高。如果这是有道理的。你能给每个孩子一个随机的高度吗,比如说一个有 40 的高度,另一个有 20、100、、、只是随机的。这看起来是唯一接近我需要实现的答案。这对随机身高的孩子有用吗,这是我的问题。
  • 当我从数据库中获取时,孩子们的身高会有所不同,而不是线性增加的高度。告诉我这是否可行,随机高度,同时知道哪个已经滚出屏幕
  • @Taio 我根据您的要求更改了代码。随机决定的高度比以前更容易。但是高度是由随机决定还是由孩子的身高决定或决定,关键的解决方法是用每个索引孩子的滚动位置和当前滚动位置计算。
  • 如果获取的数据中有确定的高度,只需将 n-1 的高度和 n 的高度相加即可生成 'itemHeight' 对象。我的例子更复杂,因为重建时随机高度会改变。
  • 添加的代码破坏了它。将 appBar 文本更改为“查看项目 $index”,以便您了解所要说的内容。它应该更新为当前正在查看的项目。现在当说索引 9 穿过屏幕时,它说“正在查看项目 8”,而它应该是“正在查看项目 9”。在我标记答案之前你能检查一下吗
【解决方案2】:

您不需要使用ScrollEndNotification。事实上,当SliverAppBar 更新 滚动时,我发现用户体验更灵敏,这与onscroll 处理程序最相似。如果您更改为仅使用常规ScrollNotification,则使用通知的想法会很好。在这种情况下,问题实际上非常简单,因为SliverList 子级具有固定 高度。孩子总是 150 像素高(120 像素是 30 填充的内容)。

考虑到这一点,我们实际上可以计算在通知处理程序中滚动出的子项的索引。我必须将 SliverAppBar 更改为 pinned 以在滚动期间保持栏(和滚动出的索引)可见。

请注意,如果子项的高度不固定,我们每次都需要计算每一行的高度,因此需要重构这种方法。

本质上的逻辑是:

// Get scroll position `progress` and subtract `SliverAppBar` height.
double progress = notification.metrics.pixels - 60;
// Calculate index scrolled off the screen.
index = (progress ~/ 150) - 1;

我还添加了一个有效的索引检查,这样就不会错误地显示负索引。

((index >= 0) ? index.toString() : "")

完整代码:

class _MyHomePageState extends State<MyHomePage> {
  late AutoScrollController controller =
      AutoScrollController(initialScrollOffset: 0);
  int index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      body: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification notification) {
          double progress = notification.metrics.pixels - 60;
          setState(() {
            index = (progress ~/ 150) - 1;
          });
          return true;
        },
        child: CustomScrollView(
          controller: controller,
          slivers: <Widget>[
            SliverAppBar(
              title: Text(
                  widget.title + " " + ((index >= 0) ? index.toString() : "")),
              expandedHeight: 60,
              pinned: true,
            ),
            SliverList(delegate: SliverChildBuilderDelegate(
              (BuildContext context, int localIndex) {
                return AutoScrollTag(
                  key: ValueKey(localIndex),
                  controller: controller,
                  index: localIndex,
                  child: Padding(
                    padding: const EdgeInsets.only(
                        top: 30.0, left: 20.0, right: 20.0),
                    child: Container(color: Colors.red, height: 120.0),
                  ),
                );
              },
            )),
          ],
        ),
      ),
    );
  }
}

【讨论】:

  • 其实真的很抱歉,因为混乱,那个容器没有固定的高度 120.0,这个高度可能会根据孩子而改变,这应该是一个实际上有几个(不同数量)的列孩子们。您的解决方案可以用于不同的高度吗
  • 嗨,@Taio 可以分享一个演示项目让我看看吗?或者,如果您可以更新问题代码,那么我将尝试一下。谢谢
  • 看这里,gist.github.com/kevinrobert3/5ab89c285bd72a2c51c6435df0774093 关键是一个列表可以包含多个项目。说 list1 有 4 个项目列表 2 有 6 个项目。它们的大小各不相同..
  • 对不起,但这个要点完全改变了整个问题!我建议改为问另一个问题。 :) 将该示例中的小部件树分解为更小的构建函数也会使问题更易于调试。谢谢。
  • 我认为这不会改变问题。我只是在问题中给出了一个最少的代码。概念是一样的,当一个项目穿过屏幕时,我需要获取项目的索引,就是这样。不过还是谢谢
猜你喜欢
  • 2021-11-04
  • 1970-01-01
  • 2020-12-18
  • 2022-10-25
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多