GridView嵌套在ListView中的cacheExtent被忽略
cacheExtent of GridView nested in ListView is ignored
我正在尝试基于随附的最小可行示例构建一个嵌套在 listview 中的 gridview,同时尝试利用内部 gridviews cacheExtent 功能,这将防止一次性构建所有项目。在完整的应用程序中,我做了一些繁重的事情,所以我希望同时加载尽可能少的项目,但不幸的是,当嵌套多个列表时,内部列表似乎忽略了 cacheExtent。对于周围列表 cacheExtent 按预期工作。
有没有人对此有解决方案并可以向我解释,但它不适用于内部列表?我不仅在寻找复制和粘贴解决方案,而且我真的想了解这里的 flutter 是怎么回事,因为我猜它是由我还不知道的任何基本布局策略引起的。
环境:
[✓] Flutter (Channel stable, 2.10.0, on macOS 12.2.1 21D62 darwin-arm, locale en-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
示例:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
// works as expected, second instance of InnerList will not be loaded on startup
cacheExtent: 1,
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return InnerList(index);
},
),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
),
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
@override
Widget build(BuildContext context) {
// indicates all widgets are getting build at once, ignoring cacheExtent
log("building widget " + index.toString());
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Foobar " + index.toString()),
GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemCount: 100,
primary: false,
// this is ignored, all items of type ItemWidget will be loaded at once
cacheExtent: 1,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ItemWidget(index);
})
],
);
}
}
//更新
已接受的答案为我指明了正确的方向,将我的搜索引导至 video 解释了为什么它不起作用 - 简单的答案是:shrinkWrap 强制列表评估所有孩子,以确定他们的身高.它甚至显示在一个非常相似的示例中,使用列表的 shrinkWrap 和物理属性。 Slivers 的解决方案现在看起来类似于以下内容,即使我仍然有点怀疑构建 CostumScrollView 在循环中使用的外部列表。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> innerLists = [];
@override
void initState() {
super.initState();
// construct lists
for(var i=0; i<10; i++) {
innerLists.add(SliverToBoxAdapter(
child: Text("Foobar " + i.toString())
));
innerLists.add(InnerList(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(slivers: innerLists),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
)
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
@override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
@override
Widget build(BuildContext context) {
return
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ItemWidget(index);
},
childCount: 100
)
);
}
}
shrinkWrap
让GridView/ListView自己决定高度。
没有shrinkWrap
,ListView无限扩展到主轴。所以它的高度受制于parent box constraint,并且填充可用space。在那种情况下 render space 是确定的。 ListView 仅在可见 space.
上构建 childs
对于shrinkWrap
,ListView尽量缩小,越短越好,除非它能包含所有child,所以它的高度是child的高度之和(大概) .在这种情况下,ListView 没有看到 parent 约束,因此 ListView 不知道它应该构建多少 child,所以它构建所有元素。
现在让我们看看你的例子。将 log
替换为 print
,我们得到以下内容。
building widget 0
building widget 1
...
building widget 99
building widget 0
building widget 1
...
building widget 99
虽然我看不到第二个InnerList
,但是ListView构建了第二个child。现在注释掉 ListView.builder
的 shrinkWrap
building widget 0
building widget 1
...
building widget 99
如果向下滚动,日志将与第一个相同。
GridView 相同。但是由于布局错误,你不能注释掉 GridView 的 shrinkWrap。这是因为 ListView 给它的 children (GridView) 赋予了无限的高度。如果 GridView 扩展,它的高度将是无穷大。
当然使用 SizedBox
会部分解决这个问题,但也许这不是您想要的。
如果你真的想在这里按需加载,你可以使用sliver。
添加:cacheExtent
不代表项目数。它是像素数。
我正在尝试基于随附的最小可行示例构建一个嵌套在 listview 中的 gridview,同时尝试利用内部 gridviews cacheExtent 功能,这将防止一次性构建所有项目。在完整的应用程序中,我做了一些繁重的事情,所以我希望同时加载尽可能少的项目,但不幸的是,当嵌套多个列表时,内部列表似乎忽略了 cacheExtent。对于周围列表 cacheExtent 按预期工作。
有没有人对此有解决方案并可以向我解释,但它不适用于内部列表?我不仅在寻找复制和粘贴解决方案,而且我真的想了解这里的 flutter 是怎么回事,因为我猜它是由我还不知道的任何基本布局策略引起的。
环境:
[✓] Flutter (Channel stable, 2.10.0, on macOS 12.2.1 21D62 darwin-arm, locale en-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
示例:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
// works as expected, second instance of InnerList will not be loaded on startup
cacheExtent: 1,
shrinkWrap: true,
itemCount: 2,
itemBuilder: (context, index) {
return InnerList(index);
},
),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
),
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
@override
Widget build(BuildContext context) {
// indicates all widgets are getting build at once, ignoring cacheExtent
log("building widget " + index.toString());
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Foobar " + index.toString()),
GridView.builder(
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemCount: 100,
primary: false,
// this is ignored, all items of type ItemWidget will be loaded at once
cacheExtent: 1,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return ItemWidget(index);
})
],
);
}
}
//更新
已接受的答案为我指明了正确的方向,将我的搜索引导至 video 解释了为什么它不起作用 - 简单的答案是:shrinkWrap 强制列表评估所有孩子,以确定他们的身高.它甚至显示在一个非常相似的示例中,使用列表的 shrinkWrap 和物理属性。 Slivers 的解决方案现在看起来类似于以下内容,即使我仍然有点怀疑构建 CostumScrollView 在循环中使用的外部列表。
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:developer';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> innerLists = [];
@override
void initState() {
super.initState();
// construct lists
for(var i=0; i<10; i++) {
innerLists.add(SliverToBoxAdapter(
child: Text("Foobar " + i.toString())
));
innerLists.add(InnerList(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(slivers: innerLists),
appBar: AppBar(
title: const Text('Home'),
),
drawer: const Drawer(
child: Text("Foobar"),
)
);
}
}
class ItemWidget extends StatelessWidget {
int index;
ItemWidget(this.index);
@override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
height: 120,
width: 120,
child: Center(child: Text(index.toString())),
);
}
}
class InnerList extends StatelessWidget {
int index;
InnerList(this.index);
@override
Widget build(BuildContext context) {
return
SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120.0,
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ItemWidget(index);
},
childCount: 100
)
);
}
}
shrinkWrap
让GridView/ListView自己决定高度。
没有shrinkWrap
,ListView无限扩展到主轴。所以它的高度受制于parent box constraint,并且填充可用space。在那种情况下 render space 是确定的。 ListView 仅在可见 space.
对于shrinkWrap
,ListView尽量缩小,越短越好,除非它能包含所有child,所以它的高度是child的高度之和(大概) .在这种情况下,ListView 没有看到 parent 约束,因此 ListView 不知道它应该构建多少 child,所以它构建所有元素。
现在让我们看看你的例子。将 log
替换为 print
,我们得到以下内容。
building widget 0
building widget 1
...
building widget 99
building widget 0
building widget 1
...
building widget 99
虽然我看不到第二个InnerList
,但是ListView构建了第二个child。现在注释掉 ListView.builder
的 shrinkWrap
building widget 0
building widget 1
...
building widget 99
如果向下滚动,日志将与第一个相同。
GridView 相同。但是由于布局错误,你不能注释掉 GridView 的 shrinkWrap。这是因为 ListView 给它的 children (GridView) 赋予了无限的高度。如果 GridView 扩展,它的高度将是无穷大。
当然使用 SizedBox
会部分解决这个问题,但也许这不是您想要的。
如果你真的想在这里按需加载,你可以使用sliver。
添加:cacheExtent
不代表项目数。它是像素数。