我希望在Flutter中有一个ListView,它提供两个方向(上,下)的延迟加载。
示例:
注意事项:
我尝试过的事情:
我希望这里有一些很聪明的人可以帮助我解决这个问题;)。几天以来,我已经在搜索并尝试该问题。谢谢!
更新
为了使事情更清楚:这是一个ListView的示例,它具有用于上下滚动的延迟加载(大多数代码是RémiRousselet从https://stackoverflow.com/a/49509349/10905712复制的):
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MyHome extends StatefulWidget {
@override
_MyHomeState createState() => new _MyHomeState();
}
class _MyHomeState extends State
ScrollController controller;
List
@override
void initState() {
super.initState();
cOntroller= new ScrollController()..addListener(_scrollListener);
}
@override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Scrollbar(
child: new ListView.builder(
controller: controller,itemBuilder: (context,index) {
return new Text(items[index]);
},itemCount: items.length,),);
}
double oldScrollPosition = 0.0;
void _scrollListener() {
bool scrollingDown = oldScrollPosition
if (controller.position.extentAfter <500 && scrollingDown) {
setState(() {
items.addAll(new List.generate(
42,(int index) => Random().nextInt(10000).toString()));
});
} else if (controller.position.extentBefore <500 && !scrollingDown) {
setState(() {
items.insertAll(
0,new List.generate(
42,(index) => Random().nextInt(10000).toString()));
});
}
oldScrollPosition = controller.position.pixels;
}
}
如果执行此代码并尝试向上滚动,则列表中将显示“跳转”。向下滚动+延迟加载效果完美。
如果ListView反转,则向上滚动+延迟加载将起作用。无论如何,使用此解决方案,我们在此处向下滚动+延迟加载会遇到相同的问题。
我只是通过稍微修改InfiniteListView库来解决它。我必须为minScrollExtent和maxScrollExtent扩展一个setter。另外,我为负索引添加了一个单独的计数:
library bidirectional_listview;
import 'dart:math' as math;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
/// BidirectionalListView
///
/// ListView that builds its children bidirectional (negative and positive indices). Scroll boundaries must be set manually by the method setMinMaxExtent
/// Code is mostly the same like https://github.com/fluttercommunity/flutter_infinite_listview/blob/master/lib/infinite_listview.dart,will add a PR soon
///
class BidirectionalListView extends StatelessWidget {
/// See [ListView.builder]
BidirectionalListView.builder({
Key key,this.scrollDirection = Axis.vertical,BidirectionalScrollController controller,this.physics,this.padding,this.itemExtent,@required IndexedWidgetBuilder itemBuilder,int itemCount,int negativeItemCount,bool addAutomaticKeepAlives = true,bool addRepaintBoundaries = true,this.anchor = 0.0,this.cacheExtent,}) : positiveChildrenDelegate = SliverChildBuilderDelegate(
itemBuilder,childCount: itemCount,addAutomaticKeepAlives: addAutomaticKeepAlives,addRepaintBoundaries: addRepaintBoundaries,),negativeChildrenDelegate = SliverChildBuilderDelegate(
(BuildContext context,int index) => itemBuilder(context,-1 - index),childCount: negativeItemCount,cOntroller= controller ?? BidirectionalScrollController(),super(key: key);
/// See [ListView.separated]
BidirectionalListView.separated({
Key key,@required IndexedWidgetBuilder separatorBuilder,}) : assert(itemBuilder != null),assert(separatorBuilder != null),itemExtent = null,positiveChildrenDelegate = SliverChildBuilderDelegate(
(BuildContext context,int index) {
final itemIndex = index ~/ 2;
return index.isEven ? itemBuilder(context,itemIndex) : separatorBuilder(context,itemIndex);
},childCount: itemCount != null ? math.max(0,itemCount * 2 - 1) : null,int index) {
final itemIndex = (-1 - index) ~/ 2;
return index.isOdd ? itemBuilder(context,super(key: key);
/// See: [ScrollView.scrollDirection]
final Axis scrollDirection;
/// See: [ScrollView.controller]
final BidirectionalScrollController controller;
/// See: [ScrollView.physics]
final ScrollPhysics physics;
/// See: [BoxScrollView.padding]
final EdgeInsets padding;
/// See: [ListView.itemExtent]
final double itemExtent;
/// See: [ScrollView.cacheExtent]
final double cacheExtent;
/// See: [ScrollView.anchor]
final double anchor;
/// See: [ListView.childrenDelegate]
final SliverChildDelegate negativeChildrenDelegate;
/// See: [ListView.childrenDelegate]
final SliverChildDelegate positiveChildrenDelegate;
@override
Widget build(BuildContext context) {
final List
final List
final AxisDirection axisDirection = _getDirection(context);
final scrollPhysics = AlwaysScrollableScrollPhysics(parent: physics);
return Scrollable(
axisDirection: axisDirection,controller: controller,physics: scrollPhysics,viewportBuilder: (BuildContext context,ViewportOffset offset) {
return Builder(builder: (BuildContext context) {
/// Build negative [ScrollPosition] for the negative scrolling [Viewport].
final state = Scrollable.of(context);
final negativeOffset = BidirectionalScrollPosition(
physics: scrollPhysics,context: state,initialPixels: -offset.pixels,keepScrollOffset: controller.keepScrollOffset,negativeScroll: true,);
/// Keep the negative scrolling [Viewport] positioned to the [ScrollPosition].
offset.addListener(() {
negativeOffset._forceNegativePixels(offset.pixels);
});
/// Stack the two [Viewport]s on top of each other so they move in sync.
return Stack(
children:
Viewport(
axisDirection: flipAxisDirection(axisDirection),anchor: 1.0 - anchor,offset: negativeOffset,slivers: negativeSlivers,cacheExtent: cacheExtent,Viewport(
axisDirection: axisDirection,anchor: anchor,offset: offset,slivers: slivers,],);
});
},);
}
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(context,scrollDirection,false);
}
List
Widget sliver;
if (itemExtent != null) {
sliver = SliverFixedExtentList(
delegate: negative ? negativeChildrenDelegate : positiveChildrenDelegate,itemExtent: itemExtent,);
} else {
sliver = SliverList(delegate: negative ? negativeChildrenDelegate : positiveChildrenDelegate);
}
if (padding != null) {
sliver = new SliverPadding(
padding:
negative ? padding - EdgeInsets.only(bottom: padding.bottom) : padding - EdgeInsets.only(top: padding.top),sliver: sliver,);
}
return
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new EnumProperty
properties
.add(new DiagnosticsProperty
properties.add(new DiagnosticsProperty
properties.add(new DiagnosticsProperty
properties.add(new DoubleProperty('itemExtent',itemExtent,defaultValue: null));
properties.add(new DoubleProperty('cacheExtent',cacheExtent,defaultValue: null));
}
}
/// Same as a [ScrollController] except it provides [ScrollPosition] objects with infinite bounds.
class BidirectionalScrollController extends ScrollController {
/// Creates a new [BidirectionalScrollController]
BidirectionalScrollController({
double initialScrollOffset = 0.0,bool keepScrollOffset = true,String debugLabel,}) : super(
initialScrollOffset: initialScrollOffset,keepScrollOffset: keepScrollOffset,debugLabel: debugLabel,);
@override
ScrollPosition createScrollPosition(ScrollPhysics physics,ScrollContext context,ScrollPosition oldPosition) {
return new BidirectionalScrollPosition(
physics: physics,context: context,initialPixels: initialScrollOffset,oldPosition: oldPosition,);
}
}
class BidirectionalScrollPosition extends ScrollPositionWithSingleContext {
BidirectionalScrollPosition({
@required ScrollPhysics physics,@required ScrollContext context,double initialPixels = 0.0,ScrollPosition oldPosition,this.negativeScroll = false,}) : assert(negativeScroll != null),super(
physics: physics,initialPixels: initialPixels,) {
_minScrollExtent = oldPosition.minScrollExtent;
_maxScrollExtent = oldPosition.maxScrollExtent;
}
final bool negativeScroll;
void _forceNegativePixels(double value) {
super.forcePixels(-value);
}
@override
double get minScrollExtent => _minScrollExtent;
double _minScrollExtent = 0.0;
@override
double get maxScrollExtent => _maxScrollExtent;
double _maxScrollExtent = 0.0;
void setMinMaxExtent(double minExtent,double maxExtent) {
_minScrollExtent = minExtent;
_maxScrollExtent = maxExtent;
}
@override
void saveScrollOffset() {
if (!negativeScroll) {
super.saveScrollOffset();
}
}
@override
void restoreScrollOffset() {
if (!negativeScroll) {
super.restoreScrollOffset();
}
}
}
以下示例演示了在上下两个方向上具有滚动边界的延迟加载:
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tiverme/ui/helpwidgets/BidirectionalListView.dart';
class MyHome extends StatefulWidget {
@override
_MyHomeState createState() => new _MyHomeState();
}
class _MyHomeState extends State
BidirectionalScrollController controller;
Map
static const double ITEM_HEIGHT = 30;
@override
void initState() {
super.initState();
for (int i = -10; i <= 10; i++) {
items[i] = "Item " + i.toString();
}
cOntroller= new BidirectionalScrollController()
..addListener(_scrollListener);
}
@override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
List
keys.sort();
int negativeItemCount = keys.first;
int itemCount = keys.last;
print("itemCount = " + itemCount.toString());
print("negativeItemCount = " + negativeItemCount.abs().toString());
return new Scaffold(
body: new Scrollbar(
child: new BidirectionalListView.builder(
controller: controller,physics: AlwaysScrollableScrollPhysics(),itemBuilder: (context,index) {
return Container(
child: Text(items[index]),height: ITEM_HEIGHT,padding: EdgeInsets.all(0),margin: EdgeInsets.all(0));
},itemCount: itemCount,negativeItemCount: negativeItemCount.abs(),);
}
void _rebuild() => setState(() {});
double oldScrollPosition = 0.0;
void _scrollListener() {
bool scrollingDown = oldScrollPosition
keys.sort();
int negativeItemCount = keys.first.abs();
int itemCount = keys.last;
double positiveReloadBorder = (itemCount * ITEM_HEIGHT - 3 * ITEM_HEIGHT);
double negativeReloadBorder =
(-(negativeItemCount * ITEM_HEIGHT - 3 * ITEM_HEIGHT));
print("pixels = " + controller.position.pixels.toString());
print("itemCount = " + itemCount.toString());
print("negativeItemCount = " + negativeItemCount.toString());
print("minExtent = " + controller.position.minScrollExtent.toString());
print("maxExtent = " + controller.position.maxScrollExtent.toString());
print("positiveReloadBorder = " + positiveReloadBorder.toString());
print("negativeReloadBorder = " + negativeReloadBorder.toString());
bool rebuildNecessary = false;
if (scrollingDown && controller.position.pixels > positiveReloadBorder) {
for (int i = itemCount + 1; i <= itemCount + 20; i++) {
items[i] = "Item " + i.toString();
}
rebuildNecessary = true;
} else if (!scrollingDown &&
controller.position.pixels
items[i] = "Item " + i.toString();
}
rebuildNecessary = true;
}
try {
BidirectionalScrollPosition pos = controller.position;
pos.setMinMaxExtent(
-negativeItemCount * ITEM_HEIGHT,itemCount * ITEM_HEIGHT);
} catch (error) {
print(error.toString());
}
if (rebuildNecessary) {
_rebuild();
}
oldScrollPosition = controller.position.pixels;
}
}