Flutter: как разрешить перекрытие контента SliverAppBar?

В Android мы используем app:behavior_overlapTop="64dp" для достижения этой цели.

введите описание изображения здесь

Я хочу, чтобы содержимое перекрывалось таким же, как и в приведенном выше GIF, в трепетании

Мой код

class DetailsPage extends StatefulWidget {
  @override
  _DetailsPage createState() => _DetailsPage();
}

class _DetailsPage extends State<DetailsPage> {
  @override
  void initState() {
    super.initState();
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
    _scrollController = ScrollController();
    _scrollController.addListener(_scrollListener);

  }

  ScrollController _scrollController;

  bool lastStatus = true;

  _scrollListener() {
    if (isShrink != lastStatus) {
      setState(() {
        lastStatus = isShrink;
      });
    }
  }

  bool get isShrink {
    return _scrollController.hasClients &&
        _scrollController.offset > (250 - kToolbarHeight);
  }

  @override
  void dispose() {
    _scrollController.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
//      backgroundColor: Colors.transparent,
      body: NestedScrollView(
        controller: _scrollController,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              expandedHeight: 250.0,
              floating: false,
              brightness: Brightness.light,
              pinned: true,
//              elevation: 0.0,
//              backgroundColor: AppColors.colorCreateTripOrange,
              backgroundColor: Colors.white,
              actions: <Widget>[
                GestureDetector(
                  onTap: () {
                    Navigator.of(context).pop();
                  },
                  child: Padding(
                    padding: const EdgeInsets.only(right: 20.0),
                    child: Image.asset(
                      'assets/images/close.png',
                      width: 25.0,
                      height: 25.0,
                    ),
                  ),
                )
              ],
              leading: Padding(
                  padding: const EdgeInsets.only(left: 20.0, top: 0),
                  child: IconButton(
                    iconSize: 25,
                    icon: Image.asset(
                      'assets/images/back.png',
                      width: 25,
                      height: 25,
                    ),
                    color: Colors.black,
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                  )),
              flexibleSpace: FlexibleSpaceBar(
                  centerTitle: false,
                  collapseMode: CollapseMode.parallax,
                  title: Text(isShrink ? "Rome" : "",
                      style: TextStyle(
                        color: isShrink ? Colors.black : Colors.white,
                        fontFamily: 'bin_bold',
                        fontSize: 18.0,
                      )),
                  background: Image.network(
                    "https://media.istockphoto.com/photos/great-colosseum-rome-italy-picture-id692334500",
                    fit: BoxFit.cover,
                  )),
            ),

          ];
        },
        body: Container(
//          padding: const EdgeInsets.only(left: 20.0, right: 20.0, top: 40.0),
          decoration: BoxDecoration(
            color: AppColors.colorWhite,
            borderRadius: BorderRadius.all(Radius.circular(20)),
          ),
          child: Column(
            children: <Widget>[
              Expanded(
                child: Container(
                  padding:
                  const EdgeInsets.only(left: 20.0, right: 20.0, top: 40.0),
                  decoration: BoxDecoration(
                    color: AppColors.colorWhite,
                    borderRadius: BorderRadius.all(Radius.circular(20)),
                  ),
                  child: ListView(
                    children: <Widget>[
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Padding(
                            padding: EdgeInsets.symmetric(horizontal: 15.0),
                            child: Text(
                              "Rome",
                              style: TextStyle(
                                  color: Colors.black,
                                  fontFamily: 'bin_bold',
                                  fontSize: 25.0),
                            ),
                          ),
                          Padding(
                            padding: EdgeInsets.only(top: 20, left: 15, right: 15),
                            child: Row(
                              children: <Widget>[
                                Image.asset(
                                  'assets/images/calender.png',
                                  width: 25.0,
                                  height: 25.0,
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(left: 10.0),
                                  child: Text(
                                    "March 6-12, 2020",
                                    style: TextStyle(
                                        fontFamily: 'bin',
                                        fontSize: 18,
                                        color: AppColors.colorActivityGray),
                                  ),
                                )
                              ],
                            ),
                          ),
                          Container(
                            width: double.infinity,
                            margin: const EdgeInsets.only(
                                top: 20.0, left: 20.0, right: 30.0),
//                padding: const EdgeInsets.only(left: 20.0, right: 20.0),
                            decoration: BoxDecoration(
                              color: AppColors.colorTripsGray,
                              borderRadius: BorderRadius.all(Radius.circular(20)),
                              border: Border.all(color: AppColors.colorDivider),
                            ),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: <Widget>[
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 15.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    Constants.region,
                                    style: TextStyle(
                                        color: AppColors.colorLightBorderOrange,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    Constants.firstName,
                                    style: TextStyle(
                                        color: AppColors.colorCreateGreyTrans,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0, bottom: 0.0, left: 0.0, right: 00.0),
                                  child: Divider(),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    Constants.activities,
                                    style: TextStyle(
                                        color: AppColors.colorLightBorderOrange,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    "Food & Bar, Must See Attractions",
                                    style: TextStyle(
                                        color: AppColors.colorCreateGreyTrans,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0, bottom: 0.0, left: 0.0, right: 00.0),
                                  child: Divider(),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    Constants.noOfTravellers,
                                    style: TextStyle(
                                        color: AppColors.colorLightBorderOrange,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    "2 Adults, 1 kid",
                                    style: TextStyle(
                                        color: AppColors.colorCreateGreyTrans,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0, bottom: 0.0, left: 0.0, right: 00.0),
                                  child: Divider(),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 0.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    Constants.email,
                                    style: TextStyle(
                                        color: AppColors.colorLightBorderOrange,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                                Padding(
                                  padding: const EdgeInsets.only(
                                      top: 10.0,
                                      bottom: 10.0,
                                      left: 20.0,
                                      right: 20.0),
                                  child: Text(
                                    "[email protected]",
                                    style: TextStyle(
                                        color: AppColors.colorCreateGreyTrans,
                                        fontFamily: 'din',
                                        fontSize: 20),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      )
                    ],
                  ),
                ),
              ),
              Container(
                width: double.infinity,
                margin: const EdgeInsets.only(top: 20.0),
                padding: const EdgeInsets.all(10.0),
                decoration: BoxDecoration(
                  color: AppColors.colorWhite,
                  borderRadius: BorderRadius.all(Radius.circular(40)),
                  border: Border.all(color: AppColors.colorDivider, width: 2.0),
                ),
                child: Center(
                  child: Wrap(
                    children: <Widget>[
                      MaterialButton(
                        padding: const EdgeInsets.symmetric(
                            horizontal: 40, vertical: 20),
                        textColor: Colors.black,
                        color: AppColors.colorWhite,
                        child: Text(
                          Constants.messageTripDesigner,
                          style: TextStyle(
                              fontFamily: 'din_bold',
                              fontSize: Constants.regionFontSize),
                        ),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(30.0),
                          side: BorderSide(
                              color: AppColors.colorLightBorderOrange,
                              width: 2),
                        ),
                        onPressed: () {
                          Navigator.of(context).pop();
                        },
                      )
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}


class MySliverAppBar extends SliverPersistentHeaderDelegate {
  final double expandedHeight;

  MySliverAppBar({@required this.expandedHeight});

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Stack(
      fit: StackFit.expand,
      overflow: Overflow.visible,
      children: [
        Image.network(
          "https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
          fit: BoxFit.cover,
        ),
        Center(
          child: Opacity(
            opacity: shrinkOffset / expandedHeight,
            child: Text(
              "MySliverAppBar",
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.w700,
                fontSize: 23,
              ),
            ),
          ),
        ),
      ],
    );
  }

  @override
  double get maxExtent => expandedHeight;

  @override
  double get minExtent => kToolbarHeight;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}

Ниже приведены некоторые сообщения, которые я пробовал до сих пор.

Если вам нужна дополнительная информация, дайте мне знать. Заранее спасибо. Ваши усилия будут оценены по достоинству.


person Goku    schedule 21.02.2020    source источник
comment
вы пробовали слушать прокрутку и анимировать панель приложений самостоятельно ?? В этом случае вам не нужна slverAppbar, достаточно обычной панели приложений с настраиваемой высотой. Вы согласны с этим решением?   -  person Darish    schedule 25.02.2020
comment
@Darish да, если у вас есть решение, напишите в ответ   -  person Goku    schedule 25.02.2020


Ответы (1)


Вы можете сделать прокрутку панели приложения с перекрывающимся содержимым, например, с гибким пространством, используя любую из следующих комбинаций в зависимости от вашего варианта использования.

  1. ListView с уведомлением о прокрутке
  2. SliverList с уведомлением о прокрутке
  3. DraggableScrollableSheet с DraggableScrollableNotification

Что ж, здесь я перейду к самому простому, DraggableScrollableSheet, который позволяет одновременно прокручивать и перетаскивать для создания желаемого эффекта.

введите описание изображения здесь

Шаги

  1. Сложите DraggableScrollableSheet и AppBar внутри каркаса.
  2. Используйте DraggableScrollableNotification для обновления высоты заголовка и AppBar тени.

Ну вот

 import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(debugShowCheckedModeBanner: false, home: HomePage()));
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  final ValueNotifier<double> headerNegativeOffset = ValueNotifier<double>(0);
  final ValueNotifier<bool> appbarShadow = ValueNotifier<bool>(false);

  final double maxHeaderHeight = 250.0;
  final double minHeaderHeight = 56.0;
  final double bodyContentRatioMin = .8;
  final double bodyContentRatioMax = 1.0;

  ///must be between min and max values of body content ratio.
  final double bodyContentRatioParallax = .9;

  @override
  void dispose() {
    headerNegativeOffset.dispose();
    appbarShadow.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //just for status bar color
      appBar: PreferredSize(
        preferredSize: Size.fromHeight(0.0),
        child: AppBar(
          backgroundColor: Colors.pink,
          elevation: 0.0,
        ),
      ),
      body: Stack(
        children: <Widget>[
          Stack(children: [
            Container(
                child: ValueListenableBuilder<double>(
                    valueListenable: headerNegativeOffset,
                    builder: (context, offset, child) {
                      return Transform.translate(
                        offset: Offset(0, offset * -1),
                        child: SizedBox(
                          height: maxHeaderHeight,
                          child: Container(
                            color: Colors.pink,
                          ),
                        ),
                      );
                    })),
            NotificationListener<DraggableScrollableNotification>(
              onNotification: (notification) {
                if (notification.extent == bodyContentRatioMin) {
                  appbarShadow.value = false;
                  headerNegativeOffset.value = 0;
                } else if (notification.extent == bodyContentRatioMax) {
                  appbarShadow.value = true;
                  headerNegativeOffset.value =
                      maxHeaderHeight - minHeaderHeight;
                } else {
                  double newValue = (maxHeaderHeight - minHeaderHeight) -
                      ((maxHeaderHeight - minHeaderHeight) *
                          ((bodyContentRatioParallax - (notification.extent)) /
                              (bodyContentRatioMax -
                                  bodyContentRatioParallax)));
                  appbarShadow.value = false;
                  if (newValue >= maxHeaderHeight - minHeaderHeight) {
                    appbarShadow.value = true;
                    newValue = maxHeaderHeight - minHeaderHeight;
                  } else if (newValue < 0) {
                    appbarShadow.value = false;
                    newValue = 0;
                  }
                  headerNegativeOffset.value = newValue;
                }

                return true;
              },
              child: Stack(
                children: <Widget>[
                  DraggableScrollableSheet(
                    initialChildSize: bodyContentRatioMin,
                    minChildSize: bodyContentRatioMin,
                    maxChildSize: bodyContentRatioMax,
                    builder: (BuildContext context,
                        ScrollController scrollController) {
                      return Stack(
                        children: <Widget>[
                          Container(
                            alignment: AlignmentDirectional.center,
                            padding: EdgeInsets.only(
                                left: 16.0, right: 16.0, top: 16.0),
                            child: Material(
                              type: MaterialType.canvas,
                              color: Colors.white,
                              elevation: 2.0,
                              borderRadius: BorderRadius.only(
                                topLeft: Radius.circular(24.0),
                                topRight: Radius.circular(24.0),
                              ),
                              child: ListView.builder(
                                controller: scrollController,
                                itemCount: 200,
                                itemBuilder: (BuildContext context, int index) {
                                  return ListTile(title: Text('Item $index'));
                                },
                              ),
                            ),
                          ),
                        ],
                      );
                    },
                  ),
                ],
              ),
            )
          ]),
          Positioned(
            left: 0.0,
            right: 0.0,
            top: 0.0,
            child: ValueListenableBuilder<bool>(
                valueListenable: appbarShadow,
                builder: (context, value, child) {
                  ///default height of appbar is 56.0. You can also
                  ///use a custom widget with custom height if you want.
                  return AppBar(
                    backgroundColor: Colors.pink,
                    title: Text("Notes"),
                    elevation: value ? 2.0 : 0.0,
                  );
                }),
          ),
        ],
      ),
      drawer: Drawer(),
    );
  }
}

См. Живую демонстрацию здесь.

person Darish    schedule 26.02.2020
comment
спасибо за ответ, я проверил это, но есть проблема, я хочу добавить изображение в качестве фона, а не розового цвета - person Goku; 28.02.2020
comment
Вы также можете использовать изображение. Без проблем. - person Darish; 28.02.2020
comment
У вас есть пример гифки? - person Darish; 28.02.2020