有没有办法在 SliverAppBar 的底部小部件中实现动态高度
Is there a way to achieve a dynamic height in bottom widget of SliverAppBar
SliverAppBar 有一个属性 bottom,它必须有 preferredSize。
现在我让它返回常数值:
...
new SliverAppBar(
expandedHeight: _kFlexibleSpaceMaxHeight,
flexibleSpace: new FlexibleSpaceBar(.....)
...
bottom: new BottomBar(...), // has to have preferredSize
),
...
class BottomBar extends StatelessWidget implements PreferredSizeWidget {
...
@override
Size get preferredSize {
return new Size.fromHeight(my_constant_height_value);
}
...
}
我想在这个底部的 Widget 中放一个文本,但我不知道里面的文本要多长。
如何实现底部小部件的动态高度?
有没有办法在布局之前测量小部件的高度?
编辑 25/04/2018
最终,我按照 Thibault 的指示进行了操作,结果如下:
// 'as rendering' to avoid conflict with 'package:intl/intl.dart'
import 'package:flutter/rendering.dart' as rendering;
...
// this is the function that returns the height of a Text widget
// given the text
double getHeight(String text, BuildContext context, bool isTitle) {
var rp = rendering.RenderParagraph(
new TextSpan(
style: isTitle
? Theme.of(context).primaryTextTheme.title
: Theme.of(context).primaryTextTheme.subhead,
text: text,
children: null,
recognizer: null),
// important as the user can have increased text on his device
textScaleFactor: MediaQuery.of(context).textScaleFactor,
textDirection: rendering.TextDirection.ltr,
);
var horizontalPaddingSum = 20; // optional
var width = MediaQuery.of(context).size.width - horizontalPaddingSum;
// if your Text widget has horizontal padding then you have to
// subtract it from available width to get the needed results
var ret = rp.computeMinIntrinsicHeight(width);
return ret;
}
...
_kPreferredBBTextHeight =
getHeight(mTitle ?? "", context, true);
var verticalPaddingSum = 10;
_kPreferredBBSubTextHeight = getHeight(mSubtitle ?? "", context,false) + verticalPaddingSum;
_kPreferredBottomBarSize =
_kPreferredBBTextHeight + _kPreferredBBSubTextHeight + 48;
_kFlexibleSpaceMaxHeight =
_kPreferredBottomBarSize + _kPreferredBottomBarSize + kToolbarHeight;
_backgroudBottomPadding = _kPreferredBottomBarSize;
...
new CustomSliverAppBar(
pinned: true,
automaticallyImplyLeading: false,
primary: true,
expandedHeight: _kFlexibleSpaceMaxHeight,
flexibleSpace: new FlexibleSpaceBar(
background: new Padding(
padding:
new EdgeInsets.only(bottom: _backgroudBottomPadding),
child: new Image(
image: new NetworkImage(mImageUrl),
fit: BoxFit.cover,
)),
),
bottom: new BottomBar(
fixedHeight: _kPreferredBottomBarSize,
),
),
...
class BottomBar extends StatelessWidget implements PreferredSizeWidget {
final double fixedHeight;
BottomBar({this.fixedHeight});
@override
Size get preferredSize {
return new Size.fromHeight(this.fixedHeight);
}
@override
Widget build(BuildContext context) {
// https://github.com/flutter/flutter/issues/3782
return new Container(
height: this.fixedHeight,
child: new Material(
color: Theme.of(context).primaryColor,
child: new Column(
children: <Widget>[
new Row(
children: <Widget>[
new IconButton(
icon: new Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.of(context).pop();
},
),
new Expanded(
child: new Container(),
),
new IconButton(
icon: new Icon(Icons.share, color: Colors.white),
onPressed: () {
print("share pressed");
},
)
],
),
new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(left: 10.0, right: 10.0),
child: new Container(
child: new Container(
alignment: Alignment.centerLeft,
child: new Text(
mTitle ?? "",
style: Theme.of(context).primaryTextTheme.title,
),
),
)),
new Container(
padding: new EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
alignment: Alignment.centerLeft,
child: new Text(
mSubtitle ?? "",
style: Theme.of(context).primaryTextTheme.subhead,
),
),
],
),
],
)));
}
PreferredSizeWidget
的重点是不,您不能动态调整此小部件的大小。
其背后的原因是 Scaffold
使用首选大小进行一些计算。如果 appbar 大小在渲染之前是未知的,那将是不可能的。
您必须相应地重新考虑您的 UI。
Is there a way to measure a widget's height before it is layed out ?
一般来说,在 build() 方法中构建 UI 时,您可以使用 LayoutBuilder,但在这种情况下它可能对您没有帮助。
在这里,您可以尝试 RenderParagraph 来渲染您的文本并在构建脚手架之前对其进行测量。您可以使用屏幕宽度作为宽度约束,布局 RenderParagraph,检索高度,并将其用作首选尺寸。
也就是说,如果您的文本在脚手架的生命周期内发生变化,您以后将无法更改首选高度。
我使用了下面的代码来解决这个问题。
toolbarHeight
是文本高度(是动态的)。
注意:此页面呈现两次。
var toolbarHeight;
BuildContext? renderBoxContext;
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
var renderBox = renderBoxContext?.findRenderObject() as RenderBox;
toolbarHeight = renderBox.size.height;
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Material(
child: getBody(context),
);
}
getBody(BuildContext context) {
var mediaQuery = MediaQuery.of(context).size;
state.toolbarHeight ??= mediaQuery.height;
return SizedBox(
width: mediaQuery.width,
height: mediaQuery.height,
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: false,
floating: true,
snap: false,
backwardsCompatibility: true,
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size(mediaQuery.width, state.toolbarHeight),
child: Builder(
builder: (ctx){
state.renderBoxContext = ctx;
return Align(
alignment: Alignment.topLeft,
child: ColoredBox(
color: Colors.green,
child: Text('line1\nline2\nline3'),
),
);
},
),
),
flexibleSpace: FlexibleSpaceBar(
title: Text('FlexibleSpaceBar'),
centerTitle: true,
collapseMode: CollapseMode.pin,
),
),
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
sliver: SliverFixedExtentList(
itemExtent: 110,
delegate: SliverChildBuilderDelegate(
(context, index) {
return Text(' item $index');
},
childCount: 10,
),
),
),
],
),
);
}
您可以使用此小部件解决此问题。
class DynamicSliverAppBar extends StatefulWidget {
final Widget child;
final double maxHeight;
DynamicSliverAppBar({
@required this.child,
@required this.maxHeight,
Key key,
}) : super(key: key);
@override
_DynamicSliverAppBarState createState() => _DynamicSliverAppBarState();
}
class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
final GlobalKey _childKey = GlobalKey();
bool isHeightCalculated = false;
double height;
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (!isHeightCalculated) {
isHeightCalculated = true;
setState(() {
height = (_childKey.currentContext.findRenderObject() as RenderBox)
.size
.height;
});
}
});
return SliverAppBar(
expandedHeight: isHeightCalculated ? height : widget.maxHeight,
flexibleSpace: FlexibleSpaceBar(
background: Column(
children: [
Container(
key: _childKey,
child: widget.child,
),
Expanded(child: SizedBox.shrink()),
],
),
),
);
}
}
SliverAppBar 有一个属性 bottom,它必须有 preferredSize。
现在我让它返回常数值:
...
new SliverAppBar(
expandedHeight: _kFlexibleSpaceMaxHeight,
flexibleSpace: new FlexibleSpaceBar(.....)
...
bottom: new BottomBar(...), // has to have preferredSize
),
...
class BottomBar extends StatelessWidget implements PreferredSizeWidget {
...
@override
Size get preferredSize {
return new Size.fromHeight(my_constant_height_value);
}
...
}
我想在这个底部的 Widget 中放一个文本,但我不知道里面的文本要多长。
如何实现底部小部件的动态高度?
有没有办法在布局之前测量小部件的高度?
编辑 25/04/2018
最终,我按照 Thibault 的指示进行了操作,结果如下:
// 'as rendering' to avoid conflict with 'package:intl/intl.dart'
import 'package:flutter/rendering.dart' as rendering;
...
// this is the function that returns the height of a Text widget
// given the text
double getHeight(String text, BuildContext context, bool isTitle) {
var rp = rendering.RenderParagraph(
new TextSpan(
style: isTitle
? Theme.of(context).primaryTextTheme.title
: Theme.of(context).primaryTextTheme.subhead,
text: text,
children: null,
recognizer: null),
// important as the user can have increased text on his device
textScaleFactor: MediaQuery.of(context).textScaleFactor,
textDirection: rendering.TextDirection.ltr,
);
var horizontalPaddingSum = 20; // optional
var width = MediaQuery.of(context).size.width - horizontalPaddingSum;
// if your Text widget has horizontal padding then you have to
// subtract it from available width to get the needed results
var ret = rp.computeMinIntrinsicHeight(width);
return ret;
}
...
_kPreferredBBTextHeight =
getHeight(mTitle ?? "", context, true);
var verticalPaddingSum = 10;
_kPreferredBBSubTextHeight = getHeight(mSubtitle ?? "", context,false) + verticalPaddingSum;
_kPreferredBottomBarSize =
_kPreferredBBTextHeight + _kPreferredBBSubTextHeight + 48;
_kFlexibleSpaceMaxHeight =
_kPreferredBottomBarSize + _kPreferredBottomBarSize + kToolbarHeight;
_backgroudBottomPadding = _kPreferredBottomBarSize;
...
new CustomSliverAppBar(
pinned: true,
automaticallyImplyLeading: false,
primary: true,
expandedHeight: _kFlexibleSpaceMaxHeight,
flexibleSpace: new FlexibleSpaceBar(
background: new Padding(
padding:
new EdgeInsets.only(bottom: _backgroudBottomPadding),
child: new Image(
image: new NetworkImage(mImageUrl),
fit: BoxFit.cover,
)),
),
bottom: new BottomBar(
fixedHeight: _kPreferredBottomBarSize,
),
),
...
class BottomBar extends StatelessWidget implements PreferredSizeWidget {
final double fixedHeight;
BottomBar({this.fixedHeight});
@override
Size get preferredSize {
return new Size.fromHeight(this.fixedHeight);
}
@override
Widget build(BuildContext context) {
// https://github.com/flutter/flutter/issues/3782
return new Container(
height: this.fixedHeight,
child: new Material(
color: Theme.of(context).primaryColor,
child: new Column(
children: <Widget>[
new Row(
children: <Widget>[
new IconButton(
icon: new Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
Navigator.of(context).pop();
},
),
new Expanded(
child: new Container(),
),
new IconButton(
icon: new Icon(Icons.share, color: Colors.white),
onPressed: () {
print("share pressed");
},
)
],
),
new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(left: 10.0, right: 10.0),
child: new Container(
child: new Container(
alignment: Alignment.centerLeft,
child: new Text(
mTitle ?? "",
style: Theme.of(context).primaryTextTheme.title,
),
),
)),
new Container(
padding: new EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
alignment: Alignment.centerLeft,
child: new Text(
mSubtitle ?? "",
style: Theme.of(context).primaryTextTheme.subhead,
),
),
],
),
],
)));
}
PreferredSizeWidget
的重点是不,您不能动态调整此小部件的大小。
其背后的原因是 Scaffold
使用首选大小进行一些计算。如果 appbar 大小在渲染之前是未知的,那将是不可能的。
您必须相应地重新考虑您的 UI。
Is there a way to measure a widget's height before it is layed out ?
一般来说,在 build() 方法中构建 UI 时,您可以使用 LayoutBuilder,但在这种情况下它可能对您没有帮助。
在这里,您可以尝试 RenderParagraph 来渲染您的文本并在构建脚手架之前对其进行测量。您可以使用屏幕宽度作为宽度约束,布局 RenderParagraph,检索高度,并将其用作首选尺寸。
也就是说,如果您的文本在脚手架的生命周期内发生变化,您以后将无法更改首选高度。
我使用了下面的代码来解决这个问题。
toolbarHeight
是文本高度(是动态的)。
注意:此页面呈现两次。
var toolbarHeight;
BuildContext? renderBoxContext;
@override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
var renderBox = renderBoxContext?.findRenderObject() as RenderBox;
toolbarHeight = renderBox.size.height;
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Material(
child: getBody(context),
);
}
getBody(BuildContext context) {
var mediaQuery = MediaQuery.of(context).size;
state.toolbarHeight ??= mediaQuery.height;
return SizedBox(
width: mediaQuery.width,
height: mediaQuery.height,
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: false,
floating: true,
snap: false,
backwardsCompatibility: true,
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size(mediaQuery.width, state.toolbarHeight),
child: Builder(
builder: (ctx){
state.renderBoxContext = ctx;
return Align(
alignment: Alignment.topLeft,
child: ColoredBox(
color: Colors.green,
child: Text('line1\nline2\nline3'),
),
);
},
),
),
flexibleSpace: FlexibleSpaceBar(
title: Text('FlexibleSpaceBar'),
centerTitle: true,
collapseMode: CollapseMode.pin,
),
),
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
sliver: SliverFixedExtentList(
itemExtent: 110,
delegate: SliverChildBuilderDelegate(
(context, index) {
return Text(' item $index');
},
childCount: 10,
),
),
),
],
),
);
}
您可以使用此小部件解决此问题。
class DynamicSliverAppBar extends StatefulWidget {
final Widget child;
final double maxHeight;
DynamicSliverAppBar({
@required this.child,
@required this.maxHeight,
Key key,
}) : super(key: key);
@override
_DynamicSliverAppBarState createState() => _DynamicSliverAppBarState();
}
class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
final GlobalKey _childKey = GlobalKey();
bool isHeightCalculated = false;
double height;
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (!isHeightCalculated) {
isHeightCalculated = true;
setState(() {
height = (_childKey.currentContext.findRenderObject() as RenderBox)
.size
.height;
});
}
});
return SliverAppBar(
expandedHeight: isHeightCalculated ? height : widget.maxHeight,
flexibleSpace: FlexibleSpaceBar(
background: Column(
children: [
Container(
key: _childKey,
child: widget.child,
),
Expanded(child: SizedBox.shrink()),
],
),
),
);
}
}