所以基本上这是一个相当老的问题,我无法通过谷歌解决。问题是
DraggableScrollableSheet
不会根据其内容大小来调整其最大大小,而只有一个静态值 maxChildSize
,如果您不想在其中看到大量空白,那么这不太好你的表。
有谁知道一些根据其内容大小设置
DragabbleScrollableSheet maxChildSize
的技巧或提供任何替代方案来解决此问题?
我创建了一个小应用程序只是为了演示该问题。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
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: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (_) => DraggableScrollableSheet(
initialChildSize: 0.8,
minChildSize: 0.3,
maxChildSize: 0.8,
expand: false,
builder: (_, controller) =>
ListView(
shrinkWrap: true,
controller: controller,
children: <Widget>[
Container(
color: Colors.red,
height: 125.0
),
Container(
color: Colors.white,
height: 125.0
),
Container(
color: Colors.green,
height: 125.0
),
],
)
)
),
child: const Text("Show scrollable sheet"),
),
],
),
),
);
}
}
我意识到问题是你无法在DraggableScrollableSheet中正确测量ListView的大小,所以我想出了一个想法,也许我应该在其他地方测量ListView。
这是我想出的办法,效率相当低,但现在总比没有好。我也想知道更好的解决方案。
class _MyHomePageState extends State<MyHomePage> {
final _key = GlobalKey();
Size? _size;
@override
void initState() {
calculateSize();
super.initState();
}
void calculateSize() =>
WidgetsBinding.instance?.addPersistentFrameCallback((_) {
_size = _key.currentContext?.size;
});
double getTheRightSize(double screenHeight) {
final maxHeight = 0.8 * screenHeight;
final calculatedHeight = _size?.height ?? maxHeight;
return calculatedHeight > maxHeight ? maxHeight / screenHeight : calculatedHeight / screenHeight;
}
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Scaffold(
body: Stack(
children: [
Opacity(
key: _key,
opacity: 0.0,
child: MeasuredWidget(),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (_) => DraggableScrollableSheet(
initialChildSize: getTheRightSize(height),
minChildSize: 0.3,
maxChildSize: getTheRightSize(height),
expand: false,
builder: (_, controller) =>
MeasuredWidget(controller: controller,)
)
),
child: const Text("Show scrollable sheet"),
),
],
),
),
],
),
);
}
}
class MeasuredWidget extends StatelessWidget {
ScrollController? controller;
MeasuredWidget({ Key? key, this.controller }) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: ListView(
shrinkWrap: true,
controller: controller,
children: <Widget>[
Container(
color: Colors.red,
height: 400.0
),
Container(
color: Colors.white,
height: 400.0
),
Container(
color: Colors.green,
height: 200.0
),
],
),
);
}
}
更新:
如果您的内容大小发生变化,您仍然希望解决该问题。因此,您必须在构建期间或构建之后测量大小,并在构建中提供 PostFrameCallback 以显示正确大小的 DraggableScrollableSheet。所以最终你的代码将如下所示:
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _key = GlobalKey();
Size? _size;
double neWheight = 100.0;
bool weClicked = false;
double getTheRightSize(double screenHeight) {
final maxHeight = 0.8 * screenHeight;
final calculatedHeight = _size?.height ?? maxHeight;
return calculatedHeight > maxHeight ? maxHeight / screenHeight : calculatedHeight / screenHeight;
}
@override
Widget build(BuildContext context) {
print("build");
final height = MediaQuery.of(context).size.height;
weClicked ? SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
print("schedule");
_size = _key.currentContext?.size;
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (_) => DraggableScrollableSheet(
initialChildSize: getTheRightSize(height),
minChildSize: 0.3,
maxChildSize: getTheRightSize(height),
expand: false,
builder: (_, controller) =>
MeasuredWidget(controller: controller, height: neWheight)
)
);
}) : Container();
return Scaffold(
body: Stack(
children: [
Opacity(
key: _key,
opacity: 0.0,
child: MeasuredWidget(height: neWheight),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () {
setState(() {
neWheight = 100;
weClicked = true;
});
},
child: const Text("Show scrollable sheet"),
),
ElevatedButton(
onPressed: () {
setState(() {
weClicked = true;
neWheight = 200;
});
},
child: const Text("Show double scrollable sheet"),
),
],
),
),
],
),
);
}
}
将控制器定义为
DraggableScrollableSheet
var dragController = DraggableScrollableController();
然后你可以像这样使用它:
DraggableScrollableSheet(
controller: controller.dragController,
initialChildSize: 0.25,
minChildSize: 0.25,
maxChildSize: 1,
/** Your code Here **/
],
),
现在可以获取DraggableScrollableSheet的大小了
dragController.size
从0到1
我可能有一个解决方案。我在
Column
和 here中使用了
SingleChildListView
和 MeasureSize
小部件来获取列的高度。渲染列后,获取列的大小,并相应调整 DragabbleScrollableSheet 的最大高度。初始工作表高度设置为 0,并使用draggableScrollController,可以在渲染列后将动画设置为 _maxSize。
class BottomSheet extends StatefulWidget {
const BottomSheet({required this.children, Key? key}) : super(key: key);
List<Widget> children;
@override
State<BottomSheet> createState() => _BottomSheetState();
}
class _BottomSheetState extends State<BottomSheet> {
DraggableScrollableController draggableScrollController = DraggableScrollableController();
double _maxSize = 1;
void _setMaxChildSize(Size size) {
setState(() {
// get height of the container.
double boxHeight = size.height;
// get height of the screen from mediaQuery.
double screenHeight = MediaQuery.of(context).size.height;
// get the ratio to set as max size.
double ratio = boxHeight/screenHeight;
_maxSize = ratio;
_animateToMaxHeight(ratio);
});
}
void _animateToMaxHeight(double ratio) {
draggableScrollController.animateTo(ratio, duration: Duration(seconds: 1), curve: Curves.linear);
}
@override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
controller: draggableScrollController,
initialChildSize: 0,
minChildSize: 0.3,
maxChildSize: _maxSize,
expand: false,
builder: (_, scrollController) =>
SingleChildScrollView(
controller: scrollController,
child: MeasureSize(
onChange: _setMaxChildSize,
child: Column(
children: widget.children;
),
),
),
);
}
}
为了完整起见,我还在此处提供了 Measure Size 小部件。
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({
Key? key,
required this.onChange,
required Widget child,
}) : super(key: key, child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return MeasureSizeRenderObject(onChange);
}
@override
void updateRenderObject(
BuildContext context, covariant MeasureSizeRenderObject renderObject) {
renderObject.onChange = onChange;
}
}
在 flutter 中获取(子)widget 的高度并不容易,只能在 widget 渲染后才能完成。 直接使用全局键获取
ListView
的大小似乎会返回 0。(我尝试过使用全局键并获取渲染框。)因此,我使用单个 SingleChildScrollView
和 Column
作为子项。这使得可以获得 Column
的大小,但只能在渲染 Column
的布局之后。