如何在 SingleChildScrollView 中使用展开?

如何在 SingleChildScrollView中使用 Expanded?我有一个屏幕与 Image.networkListView.builderRow(TextFormFieldIconButton)。我用 Expanded包裹了 ListView。如何用 SingleChildScrollView包装这个列?我需要移动屏幕时,键盘打开,看看我正在写什么。当我包装我的专栏文章时,出现了这个错误。

      body: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
child: GestureDetector(
child:
Image.network(
postOne.imageUrl,
fit: BoxFit.fitWidth,
height: MediaQuery
.of(context)
.size
.width,
width: MediaQuery
.of(context)
.size
.width,
),
onLongPress: () {},
onDoubleTap: () {},
),
),
Expanded(
//height: MediaQuery.of(context).size.width*0.33,
child: ListView.builder(
itemCount: commentList.length,
itemBuilder: (context, position) {
return GestureDetector(
onLongPress: () {},
child: Card(
child: Padding(
padding: EdgeInsets.all(5.0),
child: new CheckboxListTile(
title: new Text(commentList
.elementAt(position)
.coment,
style: TextStyle(fontSize: 18.0),),
value: values[commentList
.elementAt(position)
.coment],
onChanged: (bool value) {}),
),
)
);
}
),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new Flexible(
child: Theme(
data: new ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.grey,
inputDecorationTheme: new InputDecorationTheme(
labelStyle: new TextStyle(
color: Colors.black45, fontSize: 18.0
),
)
),
child: new Form(
key: _formKey,
child: new TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Please enter the comment';
}
},
controller: commentController,
decoration: new InputDecoration(
labelText: "Add comment",
//hintText: 'Add comment'
),
keyboardType: TextInputType.text,
),
),
),
),
new Container(
margin: EdgeInsets.only(left: 10.0, top: 12.0),
child: new IconButton(
icon: new Icon(Icons.send, color: Colors.black,),
onPressed: () {}
)
),
]),
),
],
),
),
I/flutter ( 6816): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 6816): The following assertion was thrown during performLayout():
I/flutter ( 6816): RenderFlex children have non-zero flex but incoming height constraints are unbounded.
I/flutter ( 6816): When a column is in a parent that does not provide a finite height constraint, for example if it is
I/flutter ( 6816): in a vertical scrollable, it will try to shrink-wrap its children along the vertical axis. Setting a
I/flutter ( 6816): flex on a child (e.g. using Expanded) indicates that the child is to expand to fill the remaining
I/flutter ( 6816): space in the vertical direction.
I/flutter ( 6816): These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child
I/flutter ( 6816): cannot simultaneously expand to fit its parent.
I/flutter ( 6816): Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible
I/flutter ( 6816): children (using Flexible rather than Expanded). This will allow the flexible children to size
I/flutter ( 6816): themselves to less than the infinite remaining space they would otherwise be forced to take, and
I/flutter ( 6816): then will cause the RenderFlex to shrink-wrap the children rather than expanding to fit the maximum
I/flutter ( 6816): constraints provided by the parent.
I/flutter ( 6816): The affected RenderFlex is:
I/flutter ( 6816):   RenderFlex#9f534 relayoutBoundary=up11 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 6816): The creator information is set to:
I/flutter ( 6816):   Column ← _SingleChildViewport ← IgnorePointer-[GlobalKey#3670d] ← Semantics ← Listener ←
I/flutter ( 6816):   _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#4878e] ←
I/flutter ( 6816):   Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#c5885] ← RepaintBoundary ← CustomPaint ←
I/flutter ( 6816):   ⋯
I/flutter ( 6816): The nearest ancestor providing an unbounded width constraint is:
I/flutter ( 6816):   _RenderSingleChildViewport#155d8 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-PAINT
I/flutter ( 6816):   creator: _SingleChildViewport ← IgnorePointer-[GlobalKey#3670d] ← Semantics ← Listener ←
I/flutter ( 6816):   _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#4878e] ←
I/flutter ( 6816):   Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#c5885] ← RepaintBoundary ← CustomPaint ←
I/flutter ( 6816):   RepaintBoundary ← ⋯
I/flutter ( 6816):   parentData: <none> (can use size)
I/flutter ( 6816):   constraints: BoxConstraints(0.0<=w<=440.8, 0.0<=h<=649.3)
I/flutter ( 6816):   size: MISSING
I/flutter ( 6816): See also: https://flutter.dev/layout/
I/flutter ( 6816): If this message did not help you determine the problem, consider using debugDumpRenderTree():
I/flutter ( 6816):   https://flutter.dev/debugging/#rendering-layer
I/flutter ( 6816):   http://docs.flutter.io/flutter/rendering/debugDumpRenderTree.html
I/flutter ( 6816): If none of the above helps enough to fix this problem, please don't hesitate to file a bug:
I/flutter ( 6816):   https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 6816):


screen

140682 次浏览

答案就在错误本身。当该列位于可滚动视图中时,该列试图收缩其内容,但是由于您将 Expanded作为该列的子级使用,因此该列的工作方式与试图收缩其子级的列相反。这导致了这个错误,因为这两个指令完全相反。

正如错误日志中提到的,请尝试以下操作:

考虑将 mainAxisSize设置为 MainAxisSize.min(对于列) ,并使用 FlexFit.loose适合灵活性(使用 Flexible而不是 Expanded)。

正如已经指出的,因为您使用的是可滚动的,所以您不能展开到无穷大(理论上来说) ,这就是当您试图展开嵌套在 SingleChildScrollView中的 ListView时所发生的事情。

您可以尝试使用 NestedScrollView,或者,如果它符合您的要求,并且因为您已经注释了这一行:

//height: MediaQuery.of(context).size.width*0.33,

例如,你可以把 ListView换成 ConstrainedBox(或者只是一个普通的 Container) ,而不是 Expanded,就像这样:

 Container(
height: MediaQuery.of(context).size.width*0.33,
child: ListView.builder(
itemCount: commentList.length,
...
)
)

因为您已经在一个可滚动的界面中,所以不会出现小屏幕的问题,因为整个树都是可滚动的。

试试这个,

LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
Text("Header"),
Expanded(
child: Container(
color: Colors.red,
),
),
Text("Footer"),
],
),
),
),
);
},
)

当我遇到同样的情况时,我从 Git 问题中得到了这个解决方案。我没有 Git 链接。我觉得这能帮到你。

可重用的小部件:

注意: 使用它,仅当其中一个子节点为 Expanded

import 'package:flutter/material.dart';


class ScrollColumnExpandable extends StatelessWidget {
final List<Widget> children;
final CrossAxisAlignment crossAxisAlignment;
final MainAxisAlignment mainAxisAlignment;
final VerticalDirection verticalDirection;
final TextDirection textDirection;
final TextBaseline textBaseline;
final EdgeInsetsGeometry padding;


const ScrollColumnExpandable({
Key key,
this.children,
CrossAxisAlignment crossAxisAlignment,
MainAxisAlignment mainAxisAlignment,
VerticalDirection verticalDirection,
EdgeInsetsGeometry padding,
this.textDirection,
this.textBaseline,
})  : crossAxisAlignment = crossAxisAlignment ?? CrossAxisAlignment.center,
mainAxisAlignment = mainAxisAlignment ?? MainAxisAlignment.start,
verticalDirection = verticalDirection ?? VerticalDirection.down,
padding = padding ?? EdgeInsets.zero,
super(key: key);


@override
Widget build(BuildContext context) {
final children = <Widget>[const SizedBox(width: double.infinity)];


if (this.children != null) children.addAll(this.children);
return LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: Padding(
padding: padding,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - padding.vertical,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: crossAxisAlignment,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: MainAxisSize.max,
verticalDirection: verticalDirection,
children: children,
textBaseline: textBaseline,
textDirection: textDirection,
),
),
),
),
);
},
);
}
}

我尝试了 Vijaya Ragavan 解决方案,但做了一些调整,它仍然工作。 为了在 SingleChildScrollView中使用 Expanded,我使用了 ConstrainedBox并将其高度设置为屏幕的高度(使用 MediaQuery)。您只需要确保放在 ConstrainedBox中的屏幕内容不会大于屏幕的高度。

否则,将 ConstrainedBox的高度设置为要在屏幕上显示的内容的高度。

SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Text('Hello World!'),
),
],
),
)
)


编辑: 若要减去 AppBar和/或状态栏的高度,请参见下面的步骤:

double screenHeightMinusAppBarMinusStatusBar = MediaQuery.of(context).size.height
- appBar.preferredSize.height
- MediaQuery.of(context).padding.top;

只需将 SingleChildScrollView包装在 CenterAlign元素中。

例如:

  Align(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
...
]
}
}
}

或者

  Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
...
]
}
}
}

诀窍是只在需要时应用 ScrollView,否则就让内容展开。

这种方法很有效:

class ConstrainedFlexView extends StatelessWidget {
final Widget child;
final double minSize;
final Axis axis;


const ConstrainedFlexView(this.minSize, {Key key, this.child, this.axis}) : super(key: key);


bool get isHz => axis == Axis.horizontal;


@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) {
double viewSize = isHz ? constraints.maxWidth : constraints.maxHeight;
if (viewSize > minSize) return child;
return SingleChildScrollView(
scrollDirection: axis ?? Axis.vertical,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: isHz ? double.infinity : minSize,
maxWidth: isHz ? minSize : double.infinity),
child: child,
),
);
},
);
}
}

用法:

ConstrainedFlexView(600, child: FlexContent())

这将弹性填充所有垂直空间,但一旦小部件是 < 600px,它将切换到一个受限的框 + 滚动视图,允许内容不被挤压太多。

与使用 SingleChildScrollView不同,使用 CustomScrollViewSliverFillRemaining更容易。

试试这个:

CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: <Widget>[
const Text('Header'),
Expanded(child: Container(color: Colors.red)),
const Text('Footer'),
],
),
),
],
)

如果你想要的是:

  • 能够使用 SingleChildScrollView 内部的展开来填充剩余的屏幕。
  • 不会受到键盘的干扰,也不会隐藏正在编写的 TextFormField,或者调整 SingleChildScrollView 的内容大小。

我也有同样的问题。 以下是我使用过的一种可能有害但对我而言行之有效的解决方案:

import 'package:flutter/material.dart';


class FiniteSizeSingleChildScrollViewNotBotheredByKeyboard
extends StatefulWidget {
final Widget child;


const FiniteSizeSingleChildScrollViewNotBotheredByKeyboard(
{Key? key, required this.child})
: super(key: key);


@override
State<FiniteSizeSingleChildScrollViewNotBotheredByKeyboard> createState() =>
_FiniteSizeSingleChildScrollViewNotBotheredByKeyboardState();
}


class _FiniteSizeSingleChildScrollViewNotBotheredByKeyboardState
extends State<FiniteSizeSingleChildScrollViewNotBotheredByKeyboard> {
double width = 0, height = 0;


@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
if (width != constraints.maxWidth) {
width = constraints.maxWidth;
height = constraints.maxHeight;
}
return SingleChildScrollView(
child: SizedBox(
width: width,
height: height,
child: widget.child,
),
);
});
}
}




这个想法是在 SingleChildScrollView 之前获得可用的大小,然后将这个大小注入到 SingleChildScrollView 内部的 SizedBox 中。另外,为了避免键盘改变这个大小,有一个 if 条件,它可以防止在宽度没有改变的情况下改变高度。

这个自定义小部件的唯一问题是,如果这个小部件中的一个 TextFormField 控制器(让我们称之为小部件 A)调用包含这个小部件 A 的小部件 B 上的 setState,这个小部件 A 本身是与 TextFormField 相关联的键控 Form 的子部件,控制器将触发一个重建,同时键盘将触发小部件 A 的重建,这将生成一个异常。为了避免这种情况,将键控 Form 放在小部件 A (而不是上面)中。

大多数的答案都没有考虑到你有一个文本字段的小部件,所以当键盘打开时,你会遇到内容大小的问题(它会比屏幕高) ,所以你应该至少用(灵活的)包装一个小部件在(扩展的)里面。

Scaffold(
resizeToAvoidBottomInset: true,
body:CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: false,
child: Column(
children: <Widget>[
const TextField(),
Expanded(
child: Column(
children: [
Flexible(child: someWidget()),
]
)
),
],
),
),
],
)
)

您可以简单地将该列包装在一个大小不同的框中,然后给它一个 widthheight,如下所示:

SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.9,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container() //widget here
const Expanded(
child: SizedBox(),
),
Container() //widget here
],
),