Есть ли способ добиться динамической высоты в нижнем виджете SliverAppBar
SliverAppBar имеет атрибут bottom, который должен иметь предпочитаемый размер.
Прямо сейчас у меня это возвращает постоянное значение:
...
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);
}
...
}
Я хочу поместить текст внутри этого нижнего виджета, и я не знаю, как долго будет длиться текст внутри него.
Как мне добиться динамической высоты нижнего виджета?
Есть ли способ измерить высоту виджета до его размещения?
РЕДАКТИРОВАТЬ 25/04/2018
В конце концов я последовал инструкциям Тибо и в итоге получил следующее:
// '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,
),
),
],
),
],
)));
}
5 ответов
Есть ли способ измерить высоту виджета до его размещения?
В общем случае вы можете использовать LayoutBuilder при создании пользовательского интерфейса в методе build(), но в этом случае он, вероятно, не поможет.
Здесь вы можете попробовать RenderParagraph визуализировать ваш текст и измерить его перед сборкой скаффолда. Вы можете использовать ширину экрана в качестве ограничения ширины, разметить RenderParagraph, получить высоту и использовать ее в качестве предпочтительного размера.
Тем не менее, вы не сможете изменить предпочтительную высоту позже, если ваш текст изменится в течение срока службы вашего скаффолда.
Весь смысл PreferredSizeWidget
в том, что нет, вы не можете динамически изменять размер этого виджета.
Причиной этого является Scaffold
используя этот предпочтительный размер, чтобы сделать некоторые вычисления. Что было бы невозможно, если бы размер панели приложений был неизвестен до тех пор, пока он не отобразится.
Вам придется пересмотреть свой интерфейс соответственно.
Вы можете использовать этот виджет в качестве временного решения этой проблемы.
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()),
],
),
),
);
}
}
Размер моего виджета не был статичным, поэтому мне нужен другой обходной путь. Я улучшил ответ Махди Шахбази с помощьюSizeChangedLayoutNotification
. Спасибо за внимание, подход SizedBox.shrink был умным.
class DynamicSliverAppBar extends StatefulWidget {
final Widget child;
final double maxHeight;
const 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: [
NotificationListener<SizeChangedLayoutNotification>(
onNotification: (notification) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
isHeightCalculated = true;
setState(() {
height = (_childKey.currentContext?.findRenderObject()
as RenderBox)
.size
.height;
});
});
return false;
},
child: SizeChangedLayoutNotifier(
child: Container(
key: _childKey,
child: widget.child,
),
),
),
const Expanded(
child: SizedBox.shrink(),
)
],
),
),
);
}
}
Я использовал приведенный ниже код для этой проблемы. в
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,
),
),
),
],
),
);
}