这是最新版本,感谢一些非常有用的建议
import 'dart:math';import 'package:flutter/material.dart';import 'package:flutter/scheduler.dart';import 'package:quiver/cache.dart';import 'package:quiver/collection.dart';typedef Future<List<T>> PageFuture<T>(int pageIndex);typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);typedef Widget ErrorBuilder(BuildContext context, dynamic error);class LazyListView<T> extends StatefulWidget { final int pageSize; final PageFuture<T> pageFuture; final Stream<int> countStream; final ItemBuilder<T> itemBuilder; final IndexedWidgetBuilder placeholderBuilder; final WidgetBuilder waitBuilder; final WidgetBuilder emptyResultBuilder; final ErrorBuilder errorBuilder; final double velocityThreshold; LazyListView({ @required this.pageSize, @required this.pageFuture, @required this.countStream, @required this.itemBuilder, @required this.placeholderBuilder, this.waitBuilder, this.emptyResultBuilder, this.errorBuilder, this.velocityThreshold = 128, }) : assert(pageSize > 0), assert(pageFuture != null), assert(countStream != null), assert(itemBuilder != null), assert(placeholderBuilder != null), assert(velocityThreshold >= 0); @override _LazyListViewState<T> createState() => _LazyListViewState<T>();}class _LazyListViewState<T> extends State<LazyListView<T>> { Map<int, PageResult<T>> map; MapCache<int, PageResult<T>> cache; dynamic error; int totalCount = -1; bool _frameCallbackInProgress = false; @override void initState() { super.initState(); _initCache(); widget.countStream.listen((int count) { totalCount = count; _initCache(); setState(() {}); }); } @override Widget build(BuildContext context) { //debugPrintBeginframeBanner = true; //debugPrintEndframeBanner = true; //print('build'); if (error != null && widget.errorBuilder != null) return widget.errorBuilder(context, error); if (totalCount == -1 && widget.waitBuilder != null) return widget.waitBuilder(context); if (totalCount == 0 && widget.emptyResultBuilder != null) return widget.emptyResultBuilder(context); return ListView.builder( physics: _LazyListViewPhysics(velocityThreshold: widget.velocityThreshold), itemCount: max(totalCount, 0), itemBuilder: (context, index) { // print('builder $index'); var page = index ~/ widget.pageSize; final pageResult = map[page]; final value = pageResult?.items?.elementAt(index % widget.pageSize); if (value != null) { return widget.itemBuilder(context, index, value); } // print('$index ${Scrollable.of(context).position.activity.velocity}'); if (!Scrollable.recommendDeferredLoadingForContext(context)) { cache.get(page, ifAbsent: _loadPage).then(_reload).catchError(_error); } else if (!_frameCallbackInProgress) { _frameCallbackInProgress = true; SchedulerBinding.instance.scheduleframeCallback((d) => _deferredReload(context)); } return widget.placeholderBuilder(context, index); }, ); } Future<PageResult<T>> _loadPage(int index) async { print('load $index'); var list = await widget.pageFuture(index); return PageResult(index, list); } void _initCache() { map = LruMap<int, PageResult<T>>(maximumSize: 50 ~/ widget.pageSize); cache = MapCache<int, PageResult<T>>(map: map); } void _error(dynamic e, StackTrace stackTrace) { if (widget.errorBuilder == null) { throw e; } setState(() => error = e); } void _reload(PageResult<T> value) => _doReload(value.index); void _deferredReload(BuildContext context) { print('_deferredReload'); if (!Scrollable.recommendDeferredLoadingForContext(context)) { _frameCallbackInProgress = false; _doReload(-1); } else { SchedulerBinding.instance.scheduleframeCallback((d) => _deferredReload(context), rescheduling: true); } } void _doReload(int index) { // print('reload $index'); setState(() {}); }}class PageResult<T> { /// Page index of this data. final int index; final List<T> items; PageResult(this.index, this.items);}class _LazyListViewPhysics extends AlwaysScrollableScrollPhysics { final double velocityThreshold; _LazyListViewPhysics({ @required this.velocityThreshold, ScrollPhysics parent, }) : super(parent: parent); @override recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) { // print('velocityThreshold: $velocityThreshold'); return velocity.abs() > velocityThreshold; } @override _LazyListViewPhysics applyTo(ScrollPhysics ancestor) { // print('applyTo($ancestor)'); return _LazyListViewPhysics(velocityThreshold: velocityThreshold, parent: buildParent(ancestor)); }}更新#1
这是一个新版本,可确保
setState在卸载小部件时不会调用期货。
import 'dart:async';import 'dart:math';import 'package:flutter/material.dart';import 'package:flutter/scheduler.dart';import 'package:quiver/cache.dart';import 'package:quiver/collection.dart';typedef Future<List<T>> PageFuture<T>(int pageIndex);typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);typedef Widget ErrorBuilder(BuildContext context, dynamic error);class LazyListView<T> extends StatefulWidget { final int pageSize; final PageFuture<T> pageFuture; final Stream<int> countStream; final ItemBuilder<T> itemBuilder; final IndexedWidgetBuilder placeholderBuilder; final WidgetBuilder waitBuilder; final WidgetBuilder emptyResultBuilder; final ErrorBuilder errorBuilder; final double velocityThreshold; LazyListView({ @required this.pageSize, @required this.pageFuture, @required this.countStream, @required this.itemBuilder, @required this.placeholderBuilder, this.waitBuilder, this.emptyResultBuilder, this.errorBuilder, this.velocityThreshold = 128, }) : assert(pageSize > 0), assert(pageFuture != null), assert(countStream != null), assert(itemBuilder != null), assert(placeholderBuilder != null), assert(velocityThreshold >= 0); @override _LazyListViewState<T> createState() => _LazyListViewState<T>();}class _LazyListViewState<T> extends State<LazyListView<T>> { Map<int, PageResult<T>> map; MapCache<int, PageResult<T>> cache; dynamic error; int totalCount = -1; bool _frameCallbackInProgress = false; StreamSubscription<int> countStreamSubscription; @override void initState() { super.initState(); _initCache(); countStreamSubscription = widget.countStream.listen((int count) { totalCount = count; print('totalCount = $totalCount'); _initCache(); setState(() {}); }); } @override void dispose() { countStreamSubscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { //debugPrintBeginframeBanner = true; //debugPrintEndframeBanner = true; //print('build'); if (error != null && widget.errorBuilder != null) { return widget.errorBuilder(context, error); } if (totalCount == -1 && widget.waitBuilder != null) { return widget.waitBuilder(context); } if (totalCount == 0 && widget.emptyResultBuilder != null) { return widget.emptyResultBuilder(context); } return ListView.builder( physics: _LazyListViewPhysics(velocityThreshold: widget.velocityThreshold), itemCount: max(totalCount, 0), itemBuilder: (context, index) { // print('builder $index'); final page = index ~/ widget.pageSize; final pageResult = map[page]; final value = pageResult?.items?.elementAt(index % widget.pageSize); if (value != null) { return widget.itemBuilder(context, index, value); } // print('$index ${Scrollable.of(context).position.activity.velocity}'); if (!Scrollable.recommendDeferredLoadingForContext(context)) { cache.get(page, ifAbsent: _loadPage).then(_reload).catchError(_error); } else if (!_frameCallbackInProgress) { _frameCallbackInProgress = true; SchedulerBinding.instance.scheduleframeCallback((d) => _deferredReload(context)); } return widget.placeholderBuilder(context, index); }, ); } Future<PageResult<T>> _loadPage(int index) async { print('load $index'); var list = await widget.pageFuture(index); return PageResult(index, list); } void _initCache() { map = LruMap<int, PageResult<T>>(maximumSize: 512 ~/ widget.pageSize); cache = MapCache<int, PageResult<T>>(map: map); } void _error(dynamic e, StackTrace stackTrace) { if (widget.errorBuilder == null) { throw e; } if (this.mounted) { setState(() => error = e); } } void _reload(PageResult<T> value) => _doReload(value.index); void _deferredReload(BuildContext context) { print('_deferredReload'); if (!Scrollable.recommendDeferredLoadingForContext(context)) { _frameCallbackInProgress = false; _doReload(-1); } else { SchedulerBinding.instance.scheduleframeCallback((d) => _deferredReload(context), rescheduling: true); } } void _doReload(int index) { print('reload $index'); if (this.mounted) { setState(() {}); } }}class PageResult<T> { /// Page index of this data. final int index; final List<T> items; PageResult(this.index, this.items);}class _LazyListViewPhysics extends AlwaysScrollableScrollPhysics { final double velocityThreshold; _LazyListViewPhysics({ @required this.velocityThreshold, ScrollPhysics parent, }) : super(parent: parent); @override recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) { // print('velocityThreshold: $velocityThreshold'); return velocity.abs() > velocityThreshold; } @override _LazyListViewPhysics applyTo(ScrollPhysics ancestor) { // print('applyTo($ancestor)'); return _LazyListViewPhysics(velocityThreshold: velocityThreshold, parent: buildParent(ancestor)); }}有更好的主意吗?



