在Flutter

时间:2018-09-05 08:49:20

标签: dart flutter

我正在寻找一种将新项目插入列表视图的方法,同时保持用户的滚动偏移量。基本上像拉动刷新后的Twitter提要:将新项目添加到顶部,同时保持滚动位置。然后,用户可以向上滚动以查看新添加的项目。

如果我只是在开始时用几个新项重建列表/滚动小部件,则它当然会跳转,因为滚动视图内容的高度增加了。不能估计这些新项目的高度来纠正跳跃,因为新项目的内容是可变的。 即使提供了在任意位置动态插入项目的方法的AnimatedList小部件,在插入索引0时也会跳转。

有关如何解决此问题的任何想法?也许使用Offstage小部件预先计算新物品的高度?

4 个答案:

答案 0 :(得分:1)

最近遇到了这个问题:我有一个聊天滚动,该异步滚动根据滚动的方向加载上一条或下一条消息。 This解决方案为我解决了。
解决方案的想法如下。您创建两个SliverLists并将它们放入CustomScrollView中。

CustomScrollView(
  center: centerKey,
  slivers: <Widget>[
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
            return Container(
              // Here we render elements from the upper group
              child: top[index]
            )
        }
    ),
    SliverList(
      // Key parameter makes this list grow bottom
      key: centerKey,
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
            return Container(
              // Here we render elements from the bottom group
              child: bottom[index]
            )
        }
    ),
)

第一个列表向上滚动,第二个列表向下滚动。它们的偏移零点固定在同一点,并且永远不会移动。如果需要在项目前添加项目,则将其推到顶部列表,否则,将其推到底部列表。这样,它们的偏移量就不会改变,并且滚动视图也不会跳转。
您可以在以下dartpad example中找到解决方案原型。

答案 1 :(得分:0)

我认为反向+ lazyLoading将为您提供帮助。

撤消列表:

ListView.builder(reverse: true, ...);

有关lazyLoading的信息,请参见here

答案 2 :(得分:0)

Key添加到列表项小部件。当您更改列表时,这将有助于Flutter ListView识别项目。

下面是示例虚拟代码:

ListView.builder(itemBuilder: (context, index) {
       return ListBox(someData: someData, key: Key(someData.uniqueIdentifier),);
    });

在上面的代码中,ListBox是一些自定义窗口小部件,在构造函数中具有 Key 并显示一些数据。如图所示,在创建对象时传递的唯一标识符,这将使 ListView 可以识别列表中的项目。无需做任何事情,当列表发生变化时,ListView仍然能够识别滚动视图中的项目。

答案 3 :(得分:0)

我不知道您是否能够解决它……Marcin Szalek在他的blog上发布了一个关于实现无限动态列表的非常好的解决方案。我尝试过,并且像ListView一样具有魅力。然后,我尝试使用AnimatedList进行此操作,但是遇到了与您报告的问题相同的问题(每次刷新后跳转到顶部……)。无论如何,ListView是非常强大的,应该为您解决问题! 代码是:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(primarySwatch: Colors.blue),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<int> items = List.generate(10, (i) => i);
  ScrollController _scrollController = new ScrollController();
  bool isPerformingRequest = false;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        _getMoreData();
      }
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  _getMoreData() async {
    if (!isPerformingRequest) {
      setState(() => isPerformingRequest = true);
      List<int> newEntries = await fakeRequest(
          items.length, items.length + 10); //returns empty list
      if (newEntries.isEmpty) {
        double edge = 50.0;
        double offsetFromBottom = _scrollController.position.maxScrollExtent -
            _scrollController.position.pixels;
        if (offsetFromBottom < edge) {
          _scrollController.animateTo(
              _scrollController.offset - (edge - offsetFromBottom),
              duration: new Duration(milliseconds: 500),
              curve: Curves.easeOut);
        }
      }
      setState(() {
        items.addAll(newEntries);
        isPerformingRequest = false;
      });
    }
  }

  Widget _buildProgressIndicator() {
    return new Padding(
      padding: const EdgeInsets.all(8.0),
      child: new Center(
        child: new Opacity(
          opacity: isPerformingRequest ? 1.0 : 0.0,
          child: new CircularProgressIndicator(),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: AppBar(
        title: Text("Infinite ListView"),
      ),
      body: ListView.builder(
        itemCount: items.length + 1,
        itemBuilder: (context, index) {
          if (index == items.length) {
            return _buildProgressIndicator();
          } else {
            return ListTile(title: new Text("Number $index"));
          }
        },
        controller: _scrollController,
      ),
    );
  }
}

/// from - inclusive, to - exclusive
Future<List<int>> fakeRequest(int from, int to) async {
  return Future.delayed(Duration(seconds: 2), () {
    return List.generate(to - from, (i) => i + from);
  });
}

可以找到包含整个课程的要旨here