热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

FlutterListView双向延迟加载(上,下)

我希望在Flutter中有一个ListView,它提供两个方向(上,下)的延迟加载。示例:

我希望在Flutter中有一个ListView,它提供两个方向(上,下)的延迟加载。

示例:


  • 从理论上讲,后端数据库中有60000项可以显示。

  • 首先,我要显示项目100..120

  • 从这些索引中,我希望能够在懒加载新项目的同时上下滚动

注意事项:


  • 如果达到以下时间,则顶部和底部边缘(当前索引 60000)应弹起
    滚动

我尝试过的事情:


  • Flutter ListView lazy loading中的大多数方法。这些解决方案仅适用于一个方向的延迟加载(例如,如果列表反向,则向下或向上)。如果向另一个方向滚动,则列表视图会“跳转”,因为索引已更改(旧索引1不再是新索引1)。

  • ScrollablePositionedList(https://pub.dev/documentation/flutter_widgets/latest/flutter_widgets/ScrollablePositionedList-class.html):这里的问题是,小部件要加载每个项目,例如项数为60000。无论如何,为了使此解决方案正常工作,需要itemcount。

  • IndexedListView(https://pub.dev/packages/indexed_list_view):与ScrollablePositionedList中的问题相同。无论如何,在列表顶部和底部的“弹跳”也没有用(由于缺少滚动范围)。

  • InfiniteListView(https://github.com/fluttercommunity/flutter_infinite_listview):与IndexedListView中的问题相同

我希望这里有一些很聪明的人可以帮助我解决这个问题;)。几天以来,我已经在搜索并尝试该问题。谢谢!

更新
为了使事情更清楚:这是一个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 items = new List.generate(100,(index) => 'Hello $index');
@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 print(controller.position.extentAfter);
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 slivers = _buildSlivers(context,negative: false);
final List negativeSlivers = _buildSlivers(context,negative: true);
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 _buildSlivers(BuildContext context,{bool negative = false}) {
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 [sliver];
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new EnumProperty('scrollDirection',scrollDirection));
properties
.add(new DiagnosticsProperty('controller',controller,showName: false,defaultValue: null));
properties.add(new DiagnosticsProperty('physics',physics,defaultValue: null));
properties.add(new DiagnosticsProperty('padding',padding,defaultValue: null));
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 items = new 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 = items.keys.toList();
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 List keys = items.keys.toList();
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 for (int i = -negativeItemCount - 20; i <-negativeItemCount; i++) {
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;
}
}

推荐阅读
author-avatar
MINT米田
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有