- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我已经很久没有开始搜索 Flutter ListView 库,它可以让我以智能的方式使用分页。可悲的是,我还没有找到任何符合我的标准的东西:
import 'dart:math';
import 'package:fimber/fimber.dart';
import 'package:flutter/material.dart';
typedef Future<List<T>> PageFuture<T>(int pageIndex);
typedef Widget ItemBuilder<T>(BuildContext context, int index, T entry);
typedef Widget WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);
class PagedListView<T> extends StatefulWidget {
final int pageSize;
final PageFuture<T> pageFuture;
final Stream<int> countStream;
final ItemBuilder<T> itemBuilder;
final WaitBuilder waitBuilder;
final PlaceholderBuilder placeholderBuilder;
final EmptyResultBuilder emptyResultBuilder;
final ErrorBuilder errorBuilder;
PagedListView(
{@required this.pageSize,
@required this.pageFuture,
@required this.countStream,
@required this.itemBuilder,
@required this.waitBuilder,
@required this.placeholderBuilder,
@required this.emptyResultBuilder,
@required this.errorBuilder});
@override
_PagedListView<T> createState() => _PagedListView<T>();
}
class _PagedListView<T> extends State<PagedListView<T>> {
/// Represent the number of cached pages before and after the current page.
/// If edgeCachePageCount = 1 the total number of cached pages are 3 (one before + current + one after).
/// TODO calculate from pageSize
final int edgeCachePageCount = 2;
int get maxCachedPageCount => (edgeCachePageCount * 2) + 1;
int currentPage = 0;
List<T> items;
Object error;
int totalCount = -1;
/// Contains the page indexes which the fetching is started but not completed.
final progressPages = Set<int>();
/// Contains the page indexes already retrieved.
final cachedPages = Set<int>();
int limitStartIndex = -1;
int limitEndIndex = -1;
@override
void initState() {
super.initState();
items = List.filled(widget.pageSize * maxCachedPageCount, null);
widget.countStream.listen((int count) {
Fimber.i("Total count changed: $count");
totalCount = count;
// Invalidate.
cachedPages.clear();
if (count > 0) {
_fetchPages(PageRequest.SAME);
}
setState(() {});
});
}
void _fetchPages(PageRequest pageRequest) {
Set<int> refreshIndexes = _getRefreshIndexes();
//Fimber.i("Refresh indexes are $refreshIndexes");
refreshIndexes.forEach((pageIndex) => _fetchPage(pageIndex, pageRequest));
}
Set<int> _getRefreshIndexes() {
return getRefreshIndexes(maxCachedPageCount, edgeCachePageCount, currentPage, widget.pageSize, totalCount);
}
_fetchPage(int index, PageRequest request) {
if (cachedPages.contains(index)) {
// We already have this page.
return;
}
if (!progressPages.contains(index)) {
//Fimber.i("Fetch page $index start");
progressPages.add(index);
widget.pageFuture(index).asStream().map((list) => PageResult<T>(index, request, list)).listen(_onData, onError: _onError);
}
}
void _onData(PageResult<T> data) {
if (data.items != null) {
if (!_getRefreshIndexes().contains(data.index)) {
progressPages.remove(data.index);
//Fimber.i("Skipping invalid page ${data.index}, currentPage = $currentPage, refreshIndexes = ${_getRefreshIndexes()}");
return;
}
//Fimber.i("Fetch page ${data.index} end");
if (cachedPages.length == maxCachedPageCount) {
// The cached page count is exceeded, remove the smallest / greatest page.
if (data.request == PageRequest.NEXT) {
int smallestPage = cachedPages.reduce(min);
cachedPages.remove(smallestPage);
//Fimber.i("Smallest page $smallestPage removed");
} else if (data.request == PageRequest.PREVIOUS) {
int greatestPage = cachedPages.reduce(max);
cachedPages.remove(greatestPage);
//Fimber.i("Greatest page $greatestPage removed");
} else {
int smallestPage = cachedPages.reduce(min);
int greatestPage = cachedPages.reduce(max);
int smallestPageDistance = currentPage - smallestPage;
int greatestPageDistance = greatestPage - currentPage;
if (smallestPageDistance >= greatestPageDistance) {
//Fimber.i("Smallest page $smallestPage removed, smallestPageDistance = $smallestPageDistance, greatestPageDistance = $greatestPageDistance");
cachedPages.remove(smallestPage);
} else {
//Fimber.i("Greatest page $greatestPage removed, smallestPageDistance = $smallestPageDistance, greatestPageDistance = $greatestPageDistance");
cachedPages.remove(greatestPage);
}
}
}
Set<int> tempCachedPages = cachedPages.toSet()..add(data.index);
// Put the result in the correct position.
int startIndex = widget.pageSize * (data.index % maxCachedPageCount);
items.setAll(startIndex, data.items);
//Fimber.i("Fetch page ${data.index} end, startIndex = $startIndex");
limitStartIndex = cachedPages.isEmpty ? 0 : tempCachedPages.reduce(min) * widget.pageSize;
//Fimber.i("limitStartIndex set to $limitStartIndex");
limitEndIndex = cachedPages.isEmpty ? -1 : (widget.pageSize * tempCachedPages.reduce(max)) + data.items.length - 1;
//Fimber.i("limitEndIndex set to $limitEndIndex");
cachedPages.add(data.index);
progressPages.remove(data.index);
//Fimber.i("Fetch page ${data.index} end, startIndex = $startIndex, cached pages ${cachedPages.toList()..sort()}, currentPage = $currentPage");
setState(() {});
}
}
void _onError(error) {
this.error = error;
setState(() {});
}
_fetchNewPage(int index) {
int newPage = index ~/ widget.pageSize;
PageRequest pageRequest = newPage > currentPage ? PageRequest.NEXT : (newPage < currentPage ? PageRequest.PREVIOUS : PageRequest.SAME);
/*pageRequest == PageRequest.NEXT
? Fimber.i("Fetch next page $newPage")
: (pageRequest == PageRequest.PREVIOUS ? Fimber.i("Fetch previous page $newPage") : null);*/
currentPage = newPage;
_fetchPages(pageRequest);
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
if (error != null) {
return widget.errorBuilder(context);
}
if (totalCount == -1) {
return widget.waitBuilder(context);
}
if (totalCount == 0) {
return widget.emptyResultBuilder(context);
}
return ListView.builder(
key: Key("listView"),
itemCount: totalCount,
itemBuilder: (context, index) {
if (index < limitStartIndex || index > limitEndIndex) {
_fetchNewPage(index);
}
return _getListItem(context, index);
},
);
}
Widget _getListItem(BuildContext context, int realIndex) {
int pageIndex = realIndex ~/ widget.pageSize;
if (!cachedPages.contains(pageIndex)) {
return widget.placeholderBuilder(context);
}
int cachePageIndex = pageIndex % maxCachedPageCount;
int cacheIndex = (cachePageIndex * widget.pageSize) + (realIndex % widget.pageSize);
return widget.itemBuilder(context, realIndex, items[cacheIndex]);
}
}
enum PageRequest { NEXT, PREVIOUS, SAME }
class PageResult<T> {
/// Page index of this data.
final int index;
/// Represent the direction from the current page when the request was made.
final PageRequest request;
final List<T> items;
PageResult(this.index, this.request, this.items);
}
Set<int> getRefreshIndexes(int maxCachedPageCount, int edgeCachePageCount, int currentPage, int pageSize, int totalCount) {
List<int> temp = List.generate(min(maxCachedPageCount, (totalCount ~/ pageSize) + 1), (index) => index + (currentPage - edgeCachePageCount));
int minIndex = temp.reduce(min);
if (minIndex < 0) {
return temp.map((index) => index + minIndex.abs()).toSet();
}
int maxIndex = temp.reduce(max);
int maxPage = totalCount ~/ pageSize;
if (maxIndex > maxPage) {
return temp.map((index) => index - (maxIndex - maxPage)).toSet();
}
return temp.toSet();
}
Stream<int>
每次修改数据时都会返回实际列表大小。
class MyHomePage extends StatelessWidget {
final MyDatabase database = MyDatabase();
MyHomePage({Key key}) : super(key: key);
Random random = Random.secure();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: PagedListView(
pageSize: 10,
pageFuture: (pageIndex) =>
Future.delayed(Duration(milliseconds: (random.nextDouble() * 5000).toInt()), () => database.getCategories(10, 10 * pageIndex)),
countStream: database.countCategories().watchSingle(),
itemBuilder: _itemBuilder,
waitBuilder: _waitBuilder,
placeholderBuilder: _placeholderBuilder,
emptyResultBuilder: _emptyResultBuilder,
errorBuilder: _errorBuilder,
),
);
}
Widget _itemBuilder(BuildContext context, int index, Category item) => Container(
height: 60,
child: Center(
child: ListTile(
key: Key(item.id.toString()),
title: Text(item.description),
subtitle: Text("id = ${item.id}, index = $index")
),
),
);
Widget _waitBuilder(BuildContext context) => Center(child: CircularProgressIndicator());
Widget _placeholderBuilder(BuildContext context) => Container(
height: 60,
margin: EdgeInsets.all(8),
child: Center(
child: CircularProgressIndicator(),
));
Widget _emptyResultBuilder(BuildContext context) => Container(
margin: EdgeInsets.all(8),
child: Center(
child: Text("Empty"),
));
Widget _errorBuilder(BuildContext context) => Container(
color: Colors.red,
margin: EdgeInsets.all(8),
child: Center(
child: Text("Error"),
));
}
database.getCategories(10, 10 * pageIndex))
是一个返回 Future<List<Category>>
的方法代表一个页面 database.countCategories().watchSingle()
是在每次添加/更新/删除时发出列表大小的流 import 'package:fimber/fimber.dart';
import 'package:flutter/material.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 WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);
class LazyListView<T> extends StatefulWidget {
final int pageSize;
final PageFuture<T> pageFuture;
final Stream<int> countStream;
final ItemBuilder<T> itemBuilder;
final WaitBuilder waitBuilder;
final PlaceholderBuilder placeholderBuilder;
final EmptyResultBuilder emptyResultBuilder;
final ErrorBuilder errorBuilder;
LazyListView(
{@required this.pageSize,
@required this.pageFuture,
@required this.countStream,
@required this.itemBuilder,
@required this.waitBuilder,
@required this.placeholderBuilder,
@required this.emptyResultBuilder,
@required this.errorBuilder});
@override
_LazyListView<T> createState() => _LazyListView<T>();
}
class _LazyListView<T> extends State<LazyListView<T>> {
Map<int, PageResult<T>> map;
MapCache<int, PageResult<T>> cache;
Object error;
int totalCount = -1;
int currentPage = 0;
@override
void initState() {
super.initState();
map = LruMap<int, PageResult<T>>(maximumSize: 500 ~/ widget.pageSize);
cache = MapCache<int, PageResult<T>>(map: map);
widget.countStream.listen((int count) {
Fimber.i("Total count changed: $count");
totalCount = count;
map.clear();
setState(() {});
});
}
@override
Widget build(BuildContext context) {
if (error != null) {
return widget.errorBuilder(context);
}
if (totalCount == -1) {
return widget.waitBuilder(context);
}
if (totalCount == 0) {
return widget.emptyResultBuilder(context);
}
return ListView.builder(
key: Key("listView"),
itemCount: totalCount,
itemBuilder: (context, index) {
currentPage = index ~/ widget.pageSize;
final pageResult = map[currentPage];
final value = pageResult == null ? null : pageResult.items[index % widget.pageSize];
final loading = (value == null);
if (loading) {
cache.get(currentPage, ifAbsent: _loadPage).then(reload);
return widget.placeholderBuilder(context);
}
return widget.itemBuilder(context, index, value);
},
);
}
Future<PageResult<T>> _loadPage(int index) {
Fimber.i("Start fetch page $index");
return widget.pageFuture(index).then((list) => PageResult(index, list));
}
reload(PageResult<T> value) {
// Avoid calling setState if it's not needed.
if ((value.index - currentPage).abs() > 2) {
// ATTENTION: 2 is an arbitrary value, the distance between the current page and the page in the future result should ensure correct refreshing.
// It should be greater if item widgets have a smaller height, can be smaller if item widgets have a greater height.
// TODO: make it configurable?
Fimber.i("Skipping refreshing for result of page ${value.index}, currentPage = $currentPage");
return;
}
setState(() {});
}
}
class PageResult<T> {
/// Page index of this data.
final int index;
final List<T> items;
PageResult(this.index, this.items);
}
import 'package:fimber/fimber.dart';
import 'package:flutter/material.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 WaitBuilder(BuildContext context);
typedef Widget PlaceholderBuilder(BuildContext context);
typedef Widget EmptyResultBuilder(BuildContext context);
typedef Widget ErrorBuilder(BuildContext context);
class LazyListView<T> extends StatefulWidget {
final int pageSize;
final PageFuture<T> pageFuture;
final Stream<int> countStream;
final ItemBuilder<T> itemBuilder;
final WaitBuilder waitBuilder;
final PlaceholderBuilder placeholderBuilder;
final EmptyResultBuilder emptyResultBuilder;
final ErrorBuilder errorBuilder;
LazyListView(
{@required this.pageSize,
@required this.pageFuture,
@required this.countStream,
@required this.itemBuilder,
@required this.waitBuilder,
@required this.placeholderBuilder,
@required this.emptyResultBuilder,
@required this.errorBuilder});
@override
_LazyListView<T> createState() => _LazyListView<T>();
}
class _LazyListView<T> extends State<LazyListView<T>> {
Map<int, PageResult<T>> map;
MapCache<int, PageResult<T>> cache;
Object error;
int totalCount = -1;
@override
void initState() {
super.initState();
map = LruMap<int, PageResult<T>>(maximumSize: 50 ~/ widget.pageSize);
cache = MapCache<int, PageResult<T>>(map: map);
widget.countStream.listen((int count) {
Fimber.i("Total count changed: $count");
totalCount = count;
map.clear();
setState(() {});
});
}
@override
Widget build(BuildContext context) {
if (error != null) {
return widget.errorBuilder(context);
}
if (totalCount == -1) {
return widget.waitBuilder(context);
}
if (totalCount == 0) {
return widget.emptyResultBuilder(context);
}
return ListView.builder(
key: Key("listView"),
itemCount: totalCount,
itemBuilder: (context, index) {
int currentPage = index ~/ widget.pageSize;
final pageResult = map[currentPage];
final value = pageResult == null ? null : pageResult.items[index % widget.pageSize];
final loading = (value == null);
if (loading) {
cache.get(currentPage, ifAbsent: _loadPage).then(_reload);
return widget.placeholderBuilder(context);
}
return widget.itemBuilder(context, index, value);
},
);
}
Future<PageResult<T>> _loadPage(int index) {
Fimber.i("Start fetch page $index");
return widget.pageFuture(index).then((list) => PageResult(index, list));
}
_reload(PageResult<T> value) {
if (value.refreshed) {
// Avoid calling setState if already called.
Fimber.i("Skipping refreshing for result of page ${value.index}");
return;
}
setState(() {
value.refreshed = true;
});
}
}
class PageResult<T> {
/// Page index of this data.
final int index;
final List<T> items;
bool refreshed = false;
PageResult(this.index, this.items);
}
最佳答案
由于一些非常有用的建议,这是最后一个版本
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));
}
}
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));
}
}
关于flutter - Flutter 中的分页/无限滚动,带缓存和实时失效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60074466/
我正在尝试从flutter应用程序构建apk,但出现此错误: Note: /mnt/Software/Linux/Flutter/flutter/.pub-cache/hosted/pub.dartl
我有一个名为 X 的较大应用程序,还有另一个名为 Y 的较小应用程序。他们现在彼此分开,并且工作正常。我想将应用程序 Y 集成到 X 中。我想将 Y 的代码放入 X 项目中,但它们应该有不同的 Mai
在android Studio中选择Create New Flutter Project,出现如下4个选项。 Flutter 应用程序 Flutter 插件 Flutter 包 flutter 模块
我看到我的 flutter 项目生成了一个文件 ios/Flutter/Flutter.podspec ,这个文件有什么用? 如果它与生成的 Flutter.framework 有关? 我应该将它包含
我尝试过的 在包含flutter SDK的位置添加/编辑.bash_profile.rtf文件。 导出PATH = / Users / temur / Documents / Projects / F
Flutter 日志会打印数千个详细/垃圾邮件日志。 我正在尝试调试一个复杂的应用程序,但是打印太冗长了,以至于我很难找到我自己打印的东西。 有没有办法禁用详细? 就像是: Logger.level.
在flutter 1.22更新之后,我在Lineargradient colors属性中遇到错误,这给我一个错误,即未定义名称colors参数。.在Android中更新flutter和flutter插
在下面的代码 widget.hintText 中给出错误,我试图将日期选择器作为单独的组件,并在从其他文件调用它时动态传递提示文本值。 import 'package:date_field/date_
在下面的代码 widget.hintText 中给出错误,我试图将日期选择器作为单独的组件,并在从其他文件调用它时动态传递提示文本值。 import 'package:date_field/date_
Flutter 1.0 发布后,我正在按照步骤搭建 Flutter 开发环境。 在步骤中(如所附屏幕截图所示),它说要更新 $PATH 两次,一次使用 flutter 工具的路径 export PAT
我有一个用 flutter 编写的移动应用程序,我想将其转换为 flutter_web 应用程序(集成 flutter_web 尚不可用)。我目前遇到包裹问题。 我已按照本网站中列出的说明进行操作 h
如何向我的 Flutter 路由添加自定义转换?这是我目前的路线结构。 class MyApp extends StatelessWidget { // This widget is the
我正在尝试通过 URL 在 webview 中显示网页。我试过 flutter_webview_plugin 插件,但是当我在浏览器上运行项目时它不起作用。 在 flutter web applica
我正在使用 animatedContainer 在按下按钮时显示 listView.builder()。这是一个要显示的简单子(monad)列表,但问题是我不知道 ListView 构建器的高度会传递
我目前正在我的应用程序中制作渐变背景动画......我正在使用 lottie 动画的帮助下这样做!我试图将它封装在一个容器中并成功地做到了。但是有一个问题,尽管我将高度更改为大于 2000 的东西,但
美好的一天! 我无法弄清楚如何使用 google 标签管理器设置 flutter。我找到了 this package包括标签管理器 api。但是我不知道如何正确配置它。 (在网络上我只需要复制粘贴一个
我的购物车模型如下 class Cart { String description; double unitCost; double amount; int quantity; S
在 Flutter 应用程序中,我想为在线托管的资源(图像、视频等)实现缓存。 我希望它能在原生平台 (Android/iOS)(例如使用文件系统)和网络(例如使用 IndexedDB)上运行。 Fl
我写了一个页面,在顶部一切都很好,应该是这样。在底部我有一个事件的历史。我的容器的宽度是自动确定的(取决于屏幕的宽度),而高度 - 不,在不同的设备上有不同的高度(底部的缩进是不同的)。是否可以自动确
我正在处理一个页面,其中有一些字段,例如 textfield 和 slider。在页面的末尾必须有一个用于进行下一步的按钮,该按钮被包裹在 Align 中以在页面之间具有固定位置。 另一方面,resi
我是一名优秀的程序员,十分优秀!