- 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/
运行 PostgreSQL(7.4 和 8.x),我认为这是可行的,但现在我遇到了错误。 我可以单独运行查询,它工作得很好,但如果我使用 UNION 或 UNION ALL,它会抛出错误。 这个错误:
我试图为我的应用程序创建一个导航,使用抽屉导航我的 fragment 之一(HomeFragment)有一个 ViewPager,可容纳 3 个 fragment (Bundy Clock、Annou
以我目前正在开发的应用为例: - 它有一个包含多个项目的抽屉导航;现在有两个项目让我感兴趣,我将它们称为 X 和 Y。 X 和 Y 都在单击时显示包含 x 元素或 y 元素列表的 fragment 选
我有一个形状为 (370,275,210) 的 NumPy 数组,我想将其重新整形为 (275,210,370)。我将如何在 Python 中实现这一点? 370是波段数,275是行数,210是图像包
我们如何与被子 UIViewController 阻止的父 UIViewController(具有按钮)交互。显然,触摸事件不会通过子 Nib 。 (启用用户交互) 注意:我正在加载默认和自定义 NI
我是 Jpa 新手,我想执行过程 我的代码如下 private static final String PERSISTENCE_UNIT_NAME = "todos"; private static
与安装了 LAMP 的 GCE 相比,选择与 Google Cloud SQL 链接的 GCE 实例有哪些优势? 我确定 GCE 是可扩展的,但是安装在其上的 mysql 数据库的可扩展性如何? 使用
这个问题在这里已经有了答案: Value receiver vs. pointer receiver (3 个答案) 关闭 3 年前。 我刚接触 golang。只是想了解为 Calc 类型声明的两种
我不小心按了一个快捷键,一个非常漂亮的断线出现在日期上。 有点像 # 23 Jun 2010 -------------------- 有人知道有问题的快捷方式吗?? (我在 mac 上工作!) 在
我正在Scala中编写正则表达式 val regex = "^foo.*$".r 这很好,但是如果我想做 var x = "foo" val regex = s"""^$x.*$""".r 现在我们有
以下 XML 文档在技术上是否相同? James Dean 19 和: James Dean 19 最佳答案 这两个文档在语义上是相同的。在 X
我在对数据帧列表运行稳健的线性回归模型(使用 MASS 库中的 rlm)时遇到问题。 可重现的示例: var1 <- c(1:100) var2 <- var1*var1 df1 <- data.f
好的,我有一个自定义数字键盘,可以在标签(numberField)中将数字显示为 0.00,现在我需要它显示 $0.00。 NSString *digit = sender.currentTitle;
在基于文档的应用程序中,使用 XIB 文件,创建新窗口时其行为是: 根据最后一个事件的位置进行定位和调整大小 window 。 如果最后一个事件窗口仍然可见,则新窗口 窗口应该是级联的,这样它就不会直
我想使用参数进行查询,如下所示: SELECT * FROM MATABLE WHERE MT_ID IN (368134, 181956) 所以我考虑一下 SELECT * FROM MATABLE
我遇到一些性能问题。 我有一个大约有 200 万行的表。 CREATE TABLE [dbo].[M8]( [M8_ID] [int] IDENTITY(1,1) NOT NULL,
我在 jquery 中的按键功能遇到问题。我不知道为什么按键功能不起作用。我已经使用了正确的 key 代码。在我的函数中有 2 个代码,其中包含 2 个事件键,按一个键表示 (+) 代码 107 和(
我想显示音频波形,我得到了此代码,它需要.raw音频输入并显示音频波形,但是当我放入.3gp,.mp3音频时,我得到白噪声,有人可以帮助我如何使其按需与.3gp一起使用使用.3gp音频运行它。 Inp
我无法让 stristr 函数返回真值,我相信这是因为我的搜索中有一个 $ 字符。 当我这样做时: var_dump($nopricecart); 完整的 $nopricecart 值是 $0 ,我得
如果我有这样的循环: for(int i=0;i O(n) 次。所以do some执行了O(n)次。如果做某事是线性时间,那么代码片段的复杂度是O(n^2)。 关于algorithm - 带 If 语
我是一名优秀的程序员,十分优秀!