Есть ли способ добиться динамической высоты в нижнем виджете 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,
            ),
          ),
        ),
      ],
    ),
  );
}
Другие вопросы по тегам