Listview 在Flatter中,如何创建一个从上到下都是无限的列表视图?

Listview 在Flatter中,如何创建一个从上到下都是无限的列表视图?,listview,scroll,flutter,infinite-scroll,Listview,Scroll,Flutter,Infinite Scroll,在Flatter中,如何创建一个从上到下都是无限的列表视图 换句话说,无限滚动到正负方向。更新: 截至2018年12月,只需使用以下内容: 旧答案: 像这样使用它: InfiniteListView.builder(itemBuilder: itemBuilder); 代码如下: import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; void

在Flatter中,如何创建一个从上到下都是无限的列表视图


换句话说,无限滚动到正负方向。

更新:

截至2018年12月,只需使用以下内容:

旧答案:

像这样使用它:

InfiniteListView.builder(itemBuilder: itemBuilder);
代码如下:

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';


void main() {
  runApp(MaterialApp(home: HomePage()));
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //
    List<double> heights =
        new List<double>.generate(100, (i) => Random().nextInt(90).toDouble() + 45.0);

    var itemBuilder = (BuildContext context, int index) {
      return Card(
        child: Container(
          height: heights[index % 100],
          color: Colors.green,
          child: Center(
              child: MaterialButton(
            color: Colors.grey,
            child: Text('ITEM $index'),
            onPressed: () => print("PRESSED $index"),
          )),
        ),
      );
    };

    return Scaffold(
      appBar: AppBar(title: const Text('Infinite ListView')),
      body: InfiniteListView.builder(itemBuilder: itemBuilder),
    );
  }
}

//////////////////////////////////////////////////////////////////

class InfiniteListView extends StatefulWidget {
  //
  const InfiniteListView.builder({Key key, this.itemBuilder}) : super(key: key);

  final IndexedWidgetBuilder itemBuilder;

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

class _UnboundedScrollPosition extends ScrollPositionWithSingleContext {
  _UnboundedScrollPosition({
    ScrollPhysics physics,
    ScrollContext context,
    ScrollPosition oldPosition,
    double initialPixels,
  }) : super(
          physics: physics,
          context: context,
          oldPosition: oldPosition,
          initialPixels: initialPixels,
        );

  @override
  double get minScrollExtent => double.negativeInfinity;

  /// There is a feedback-loop between aboveController and belowController. When one of them is
  /// being used, it controlls the other. However if they get out of sync, for timing reasons,
  /// the controlled one with try to controll the other, and the jump will stop the real controller.
  /// For this reason, we can't let one stop the other (idle and ballistics) in this situattion.
  void jumpToWithoutGoingIdleAndKeepingBallistic(double value) {
    if (pixels != value) {
      forcePixels(value);
    }
  }
}

class _UnboundedScrollController extends ScrollController {
  //
  _UnboundedScrollController({
    double initialScrollOffset = 0.0,
    keepScrollOffset = true,
    debugLabel,
  }) : super(
            initialScrollOffset: initialScrollOffset,
            keepScrollOffset: keepScrollOffset,
            debugLabel: debugLabel);

  @override
  _UnboundedScrollPosition createScrollPosition(
    ScrollPhysics physics,
    ScrollContext context,
    ScrollPosition oldPosition,
  ) {
    return _UnboundedScrollPosition(
      physics: physics,
      context: context,
      oldPosition: oldPosition,
      initialPixels: initialScrollOffset,
    );
  }

  void jumpToWithoutGoingIdleAndKeepingBallistic(double value) {
    assert(positions.isNotEmpty, 'ScrollController not attached.');
    for (_UnboundedScrollPosition position in new List<ScrollPosition>.from(positions))
      position.jumpToWithoutGoingIdleAndKeepingBallistic(value);
  }
}

class _InfinitListViewState extends State<InfiniteListView> {
  //
  _UnboundedScrollController _positiveController;
  _UnboundedScrollController _negativeController;

  @override
  void initState() {
    super.initState();

    // Instantiate the negative and positive list positions, relative to one another.
    WidgetsBinding.instance.addPostFrameCallback((_) => _negativeController
        .jumpTo(-_negativeController.position.extentInside - _positiveController.position.pixels));

    _positiveController?.dispose();
    _negativeController?.dispose();
    _positiveController = _UnboundedScrollController(keepScrollOffset: false);
    _negativeController = _UnboundedScrollController();

    // ---

    // The POSITIVE list moves the NEGATIVE list, but only if the NEGATIVE list position would change.
    _positiveController.addListener(() {
      var newNegativePosition =
          -_negativeController.position.extentInside - _positiveController.position.pixels;
      var oldNegativePosition = _negativeController.position.pixels;

      if (newNegativePosition != oldNegativePosition) {
        _negativeController.jumpToWithoutGoingIdleAndKeepingBallistic(newNegativePosition);
      }
    });

    // ---

    // The NEGATIVE list moves the POSITIVE list, but only if the POSITIVE list position would change.
    _negativeController.addListener(() {
      var newBelowPosition =
          -_positiveController.position.extentInside - _negativeController.position.pixels;
      var oldBelowPosition = _positiveController.position.pixels;

      if (newBelowPosition != oldBelowPosition) {
        _positiveController.jumpToWithoutGoingIdleAndKeepingBallistic(newBelowPosition);
      }
    });
  }

  @override
  void dispose() {
    _positiveController.dispose();
    _negativeController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    //
    var sliverList = SliverList(
      delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
        return widget.itemBuilder(context, -index - 1);
      }),
    );

    var negativeList = CustomScrollView(
      physics: const AlwaysScrollableScrollPhysics(),
      controller: _negativeController,
      reverse: true,
      slivers: [sliverList],
    );

    var positiveList = ListView.builder(
      controller: _positiveController,
      itemBuilder: (BuildContext context, int index) {
        return widget.itemBuilder(context, index);
      },
    );

    return Stack(
      children: <Widget>[
        negativeList,
        _ControlledIgnorePointer(
          child: positiveList,
          controller: _positiveController,
        ),
      ],
    );
  }
}

class _ControlledIgnorePointer extends SingleChildRenderObjectWidget {
  //
  final ScrollController controller;

  const _ControlledIgnorePointer({Key key, @required this.controller, Widget child})
      : assert(controller != null),
        super(key: key, child: child);

  @override
  _ControlledRenderIgnorePointer createRenderObject(BuildContext context) {
    return new _ControlledRenderIgnorePointer(controller: controller);
  }

  @override
  void updateRenderObject(BuildContext context, _ControlledRenderIgnorePointer renderObject) {
    renderObject..controller = controller;
  }
}

/// Render object that is invisible to hit testing in offsets that depend on the controller.
class _ControlledRenderIgnorePointer extends RenderProxyBox {
  _ControlledRenderIgnorePointer({RenderBox child, ScrollController controller})
      : _controller = controller,
        super(child) {
    assert(_controller != null);
  }

  ScrollController get controller => _controller;
  ScrollController _controller;
  set controller(ScrollController value) {
    assert(value != null);
    if (value == _controller) return;
    _controller = value;
  }

  @override
  bool hitTest(HitTestResult hitTestResult, {Offset position}) {
    bool ignore = -controller.position.pixels > position.dy;
    var boolResult = ignore ? false : super.hitTest(hitTestResult, position: position);
    return boolResult;
  }
}
import'dart:math';
进口“包装:颤振/材料.省道”;
导入“package:flatter/rendering.dart”;
void main(){
runApp(MaterialApp(home:HomePage());
}
类主页扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
//
列表高度=
新列表.generate(100,(i)=>Random().nextInt(90.toDouble()+45.0);
var itemBuilder=(BuildContext上下文,int索引){
回程卡(
子:容器(
高度:高度[索引%100],
颜色:颜色。绿色,
儿童:中心(
子:材质按钮(
颜色:颜色。灰色,
子项:文本('ITEM$index'),
onPressed:()=>打印(“PRESSED$index”),
)),
),
);
};
返回脚手架(
appBar:appBar(标题:常量文本(“无限列表视图”),
body:InfiniteListView.builder(itemBuilder:itemBuilder),
);
}
}
//////////////////////////////////////////////////////////////////
类InfiniteListView扩展了StatefulWidget{
//
const InfiniteListView.builder({Key-Key,this.itemBuilder}):super(Key:Key);
最终索引WidgetBuilder itemBuilder;
@凌驾
_InfinitiListViewState createState()=>InfinitiListViewState();
}    
类_UnboundedScrollPosition使用SingleContext扩展ScrollPosition{
_无界CrollPosition({
物理,,
滚动上下文上下文,
滚动位置oldPosition,
双初始像素,
}):超级(
物理学:物理学,
上下文:上下文,
oldPosition:oldPosition,
初始像素:初始像素,
);
@凌驾
double get minScrollExtent=>double.negativeInfinity;
///上面的控制器和下面的控制器之间有一个反馈回路。当其中一个控制器
///使用时,它控制另一个。但是如果它们不同步,出于时间原因,
///被控制的一方试图控制另一方,跳转将停止真正的控制器。
///因此,在这种情况下,我们不能让一方阻止另一方(空转和弹道)。
无效跳转,无需跳转并保持弹道(双值){
如果(像素!=值){
像素(值);
}
}
}
类_无界ScrollController扩展ScrollController{
//
_无界croll控制器({
双初始滚动偏移=0.0,
keepscroloffset=true,
调试标签,
}):超级(
initialScrollOffset:initialScrollOffset,
keepscroloffset:keepscroloffset,
debugLabel:debugLabel);
@凌驾
_无界CrollPosition CreateCrollPosition(
物理,,
滚动上下文上下文,
滚动位置oldPosition,
) {
返回_无界CrollPosition(
物理学:物理学,
上下文:上下文,
oldPosition:oldPosition,
initialPixels:initialScrollOffset,
);
}
无效跳转,无需跳转并保持弹道(双值){
断言(positions.isNotEmpty,“ScrollController未连接”);
对于(_新列表中的无界CrollPosition位置。从(位置))
位置。跳到无引线且保持弹道(值);
}
}
类∞ListViewState扩展了状态{
//
_无界CrollController _正控制器;
_无界CrollController _negativeController;
@凌驾
void initState(){
super.initState();
//实例化消极和积极的列表位置,相对于彼此。
WidgetsBinding.instance.addPostFrameCallback((\u)=>\ u NegativeControl
.jumpTo(-_NegativeControl.position.extentInside-_PositiveControl.position.pixels));
_正控制器?.dispose();
_负控制器?.dispose();
_positiveController=\u无界CrollController(keepScrolOffset:false);
_negativeController=_UnboundedScrollController();
// ---
//肯定列表移动否定列表,但前提是否定列表的位置会改变。
_positiveController.addListener((){
新负位=
-_NegativeControl.position.extentInside-_PositiveControl.position.pixels;
var oldNegativePosition=_negativeController.position.pixels;
if(newNegativePosition!=旧NegativePosition){
_NegativeControl。无需前进即可跳跃并保持弹道(新NegativePosition);
}
});
// ---
//负面列表移动正面列表,但前提是正面列表位置会发生变化。
_NegativeControl.addListener((){
var newBelowPosition=
-_PositiveControl.position.extentInside-_NegativeControl.position.pixels;
var oldblowposition=_positiveController.position.pixels;
如果(新的belowPosition!=旧的belowPosition){
_正向控制器。无需牵引即可跳跃并保持弹道(新位置);
}
});
}
@凌驾
无效处置(){
_positiveController.dispose();
_negativeController.dispose();
super.dispose();
}
@凌驾
小部件构建(构建上下文){
//
var sliverList=sliverList(
委托:SliverChildBuilderDelegate((BuildContext上下文,int索引){
返回widget.itemBuilder(上下文,-index-1);
}),
);
var negativeList=CustomScrollView(
物理:常量AlwaysScrollablePhysics(),
控制器:_负控制器,
相反:是的,
条子:[条子列表],
);
var positiveList=ListView.builder(
控制器:_正控制器,
itemBuilder:(构建上下文,int索引){
返回widget.itemBuilder(上下文