Listview 在Flatter中,如何创建一个从上到下都是无限的列表视图?
在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
换句话说,无限滚动到正负方向。更新: 截至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(上下文