Flutter TabBar и SliverAppBar, который скрывается при прокрутке вниз - PullRequest
4 голосов
/ 15 марта 2019

Я пытаюсь создать приложение с верхней панелью приложений и панелью вкладок ниже.При прокрутке вниз панель должна скрываться, перемещаясь за пределы экрана (но вкладки должны оставаться), а когда вы прокручиваете назад вверх, панель приложения должна отображаться снова.Такое поведение можно увидеть в WhatsApp.Пожалуйста, посмотрите это видео для демонстрации.(Взято из Material.io ). Это аналогичное поведение, хотя панель прокрутки и панель вкладок скрыты при прокрутке, поэтому это не совсем то поведение, которое я ищу.

Мне удалось добиться автоматического скрытияОднако есть несколько проблем:

  1. Я должен установить snap из SliverAppBar на true.Без этого панель приложения не будет отображаться при прокрутке назад.

    Хотя это работает, это не то поведение, которое я ищу.Я хочу, чтобы панель приложения отображалась плавно (аналогично WhatsApp), а не появлялась в поле зрения, даже если вы прокручиваете очень мало.

  2. Когда я прокручиваю вниз и меняю вкладки, немногосодержимое вырезано из поля зрения.

    Ниже приведен GIF, показывающий поведение:

    GIF demonstrating output

    (См. часть, когда я прокручиваю внизв listView (tab1), затем вернитесь к tab2)

Вот код для DefaultTabController:

DefaultTabController(
  length: 2,
  child: new Scaffold(
    body: new NestedScrollView(
      headerSliverBuilder:
          (BuildContext context, bool innerBoxIsScrolled) {
        return <Widget>[
          new SliverAppBar(
            title: Text("Application"),
            floating: true,
            pinned: true,
            snap: true,    // <--- this is required if I want the application bar to show when I scroll up
            bottom: new TabBar(
              tabs: [ ... ],    // <-- total of 2 tabs
            ),
          ),
        ];
      },
      body: new TabBarView(
        children: [ ... ]    // <--- the array item is a ListView
      ),
    ),
  ),
),

В случае необходимости,полный код находится в этом GitHub репозитории .main.dart здесь здесь .

Я также нашел этот связанный вопрос: Скрыть панель приложений на Scroll Flutter? .Однако, это не обеспечило решение.Те же проблемы сохраняются, и при прокрутке вверх SliverAppBar не будет отображаться.(Так что snap: true требуется)

Я также обнаружил эту проблему на GitHub Flutter.( Редактировать: кто-то сказал, что ждет, пока команда Flutter исправит это. Есть ли вероятность, что решения не существует?)

Это вывод flutter doctor -v: Pastebin .Определенные проблемы найдены, но из того, что я узнал, они не должны оказывать влияние.

Ответы [ 2 ]

4 голосов
/ 17 марта 2019

--- РЕДАКТИРОВАТЬ 1 -

Хорошо, поэтому я собрал кое-что быстрое для вас.Я следил за этой статьей (написанной Эмили Фортуной, которая является одним из главных разработчиков Flutter), чтобы лучше понять Slivers.

Medium: Slivers, Demystific

Но потом нашел этоYoutube видео, которое в основном использовало ваш код, поэтому я выбрал этот, а не пытался выяснить каждую мелочь о Slivers.

Youtube: Использование контроллеров Tab и Scroll и NestedScrollView в Dart's Flutter Framework

Оказывается, вы были на правильном пути со своим кодом.Вы можете использовать SliverAppBar в NestedScrollView (это было не так в прошлый раз, когда я пытался), но я сделал несколько изменений.Это я объясню после моего кода:

import 'package:flutter/material.dart';

import 'dart:math';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>  with SingleTickerProviderStateMixin /*<-- This is for the controllers*/ {
  TabController _tabController; // To control switching tabs
  ScrollController _scrollViewController; // To control scrolling

  List<String> items = [];
  List<Color> colors = [Colors.red, Colors.green, Colors.yellow, Colors.purple, Colors.blue, Colors.amber, Colors.cyan, Colors.pink];
  Random random = new Random();

  Color getRandomColor() {
    return colors.elementAt(random.nextInt(colors.length));
  }

  @override
  void initState() {
    super.initState();
    _tabController =TabController(vsync: this, length: 2);
    _scrollViewController =ScrollController();
  }

  @override
  void dispose() {
    super.dispose();
    _tabController.dispose();
    _scrollViewController.dispose();
  }

  @override
  Widget build(BuildContext context) {

 // Init the items
    for (var i = 0; i < 100; i++) {
      items.add('Item $i');
    }

    return SafeArea(
      child: NestedScrollView(
        controller: _scrollViewController,
        headerSliverBuilder: (BuildContext context, bool boxIsScrolled) {
          return <Widget>[
            SliverAppBar(
              title: Text("WhatsApp using Flutter"),
              floating: true,
              pinned: false,
              snap: true,
              bottom: TabBar(
                tabs: <Widget>[
                  Tab(
                    child: Text("Colors"),
                  ),
                  Tab(
                    child: Text("Chats"),
                  ),
                ],
                controller: _tabController,
              ),
            ),
          ];
        },
        body: TabBarView(
              controller: _tabController,
              children: <Widget>[
                ListView.builder(
                  itemBuilder: (BuildContext context, int index) {
                      Color color = getRandomColor();
                      return Container(
                        height: 150.0,
                        color: color,
                        child: Text(
                          "Row $index",
                          style: TextStyle(
                            color: Colors.white,
                          ),
                        ),
                      );
                    },
                    //physics: NeverScrollableScrollPhysics(), //This may come in handy if you have issues with scrolling in the future
                  ),

                  ListView.builder(
                    itemBuilder: (BuildContext context, int index) {
                      return Material(
                        child: ListTile(
                          leading: CircleAvatar(
                            backgroundColor: Colors.blueGrey,
                          ),
                          title: Text(
                            items.elementAt(index)
                            ),
                        ),
                      );
                    },
                    //physics: NeverScrollableScrollPhysics(),
                  ),
              ],
            ),
      ),
    );

  }
}

Хорошо, так далее до объяснения.

  1. Используйте StatefulWidget

    Большинство виджетов во Флаттере будут с состоянием, но это зависит от ситуации.Я думаю, что в этом случае это лучше, потому что вы используете ListView, который может измениться, когда пользователи добавляют или стирают разговоры / чаты.

  2. SafeArea, потому что этот виджетотлично.

    Читайте об этом на Документы по флаттеру: SafeArea

  3. Контроллеры

    Сначала я думаю, что это была большая проблема, но, возможно, это было что-то еще.Но вы обычно должны создавать свои собственные контроллеры, если вы имеете дело с пользовательским поведением во Флаттере.Таким образом, я сделал _tabController и _scrollViewController (я не думаю, что я получил все функциональные возможности, то есть отслеживание позиций прокрутки между вкладками, но они работают для основ).Контроллер табуляции, который вы используете для TabBar и TabView, должен быть одинаковым.

  4. Виджет Material перед ListTile

    Возможно, вы бы узнали об этом рано или поздно, но виджет ListTile является виджетом Материала и поэтому требует «виджета предка материала» в соответствии с выводом, который я получил при первой попытке его визуализации.Так что я избавил тебя от крошечной головной боли.Я думаю, это потому, что я не использовал Scaffold.(Просто имейте это в виду, когда вы используете виджеты материалов без виджетов предшественников материалов)

Надеюсь, это поможет вам начать работу, если вам нужна помощь, просто напишите мне или добавьте меня в свойGithub репо, и я посмотрю, что я могу сделать.


--- ОРИГИНАЛ ---

Я также ответил вам на Reddit, надеюсь, вы скоро увидите один из этих двух.

SliverAppBar Info

Ключевые свойства, которые вы хотите иметь с SliverAppBar:

floating: Whether the app bar should become visible as soon as the user scrolls towards the app bar.
pinned: Whether the app bar should remain visible at the start of the scroll view. (This is the one you are asking about)
snap: If snap and floating are true then the floating app bar will "snap" into view.

Все это взято из Flutter SliverAppBar Docs .У них есть много анимированных примеров с различными комбинациями плавающих, закрепленных и привязанных.

Так что для вас должно работать следующее:

SliverAppBar(
            title: Text("Application"),
            floating: true, // <--- this is required if you want the appbar to come back into view when you scroll up
            pinned: false, // <--- this will make the appbar disappear on scrolling down
            snap: true,    // <--- this is required if you want the application bar to 'snap' when you scroll up (floating MUST be true as well)
            bottom: new TabBar(
              tabs: [ ... ],    // <-- total of 2 tabs
            ),
          ),

ScrollView с SliverAppBar

Чтобы ответить на основной вопрос NestedScrollView.Согласно документам (таким же, как указано выше) SliverAppBar - это:

Панель приложения дизайна материала, которая интегрируется с CustomScrollView.

Поэтомувы не можете использовать NestedScrollView, вам нужно использовать CustomScrollView. Это предназначено для использования Sliver классов, но они могут использоваться в NestedScrollView Ознакомьтесь с документами .

2 голосов
/ 22 марта 2019

Вам необходимо использовать SliverOverlapAbsorber / SliverOverlapInjector , у меня работает следующий код ( Полный код ):

@override
  Widget build(BuildContext context) {
    return Material(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length, // This is the number of tabs.
          child: NestedScrollView(
            headerSliverBuilder:
                (BuildContext context, bool innerBoxIsScrolled) {
              // These are the slivers that show up in the "outer" scroll view.
              return <Widget>[
                SliverOverlapAbsorber(
                  // This widget takes the overlapping behavior of the SliverAppBar,
                  // and redirects it to the SliverOverlapInjector below. If it is
                  // missing, then it is possible for the nested "inner" scroll view
                  // below to end up under the SliverAppBar even when the inner
                  // scroll view thinks it has not been scrolled.
                  // This is not necessary if the "headerSliverBuilder" only builds
                  // widgets that do not overlap the next sliver.
                  handle:
                      NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  child: SliverSafeArea(
                    top: false,
                    sliver: SliverAppBar(
                      title: const Text('Books'),
                      floating: true,
                      pinned: true,
                      snap: false,
                      primary: true,
                      forceElevated: innerBoxIsScrolled,
                      bottom: TabBar(
                        // These are the widgets to put in each tab in the tab bar.
                        tabs: _tabs.map((String name) => Tab(text: name)).toList(),
                      ),
                    ),
                  ),
                ),
              ];
            },
            body: TabBarView(
              // These are the contents of the tab views, below the tabs.
              children: _tabs.map((String name) {
                return SafeArea(
                  top: false,
                  bottom: false,
                  child: Builder(
                    // This Builder is needed to provide a BuildContext that is "inside"
                    // the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
                    // find the NestedScrollView.
                    builder: (BuildContext context) {
                      return CustomScrollView(
                        // The "controller" and "primary" members should be left
                        // unset, so that the NestedScrollView can control this
                        // inner scroll view.
                        // If the "controller" property is set, then this scroll
                        // view will not be associated with the NestedScrollView.
                        // The PageStorageKey should be unique to this ScrollView;
                        // it allows the list to remember its scroll position when
                        // the tab view is not on the screen.
                        key: PageStorageKey<String>(name),
                        slivers: <Widget>[
                          SliverOverlapInjector(
                            // This is the flip side of the SliverOverlapAbsorber above.
                            handle:
                                NestedScrollView.sliverOverlapAbsorberHandleFor(
                                    context),
                          ),
                          SliverPadding(
                            padding: const EdgeInsets.all(8.0),
                            // In this example, the inner scroll view has
                            // fixed-height list items, hence the use of
                            // SliverFixedExtentList. However, one could use any
                            // sliver widget here, e.g. SliverList or SliverGrid.
                            sliver: SliverFixedExtentList(
                              // The items in this example are fixed to 48 pixels
                              // high. This matches the Material Design spec for
                              // ListTile widgets.
                              itemExtent: 60.0,
                              delegate: SliverChildBuilderDelegate(
                                (BuildContext context, int index) {
                                  // This builder is called for each child.
                                  // In this example, we just number each list item.
                                  return Container(
                                      color: Color((math.Random().nextDouble() *
                                                      0xFFFFFF)
                                                  .toInt() <<
                                              0)
                                          .withOpacity(1.0));
                                },
                                // The childCount of the SliverChildBuilderDelegate
                                // specifies how many children this inner list
                                // has. In this example, each tab has a list of
                                // exactly 30 items, but this is arbitrary.
                                childCount: 30,
                              ),
                            ),
                          ),
                        ],
                      );
                    },
                  ),
                );
              }).toList(),
            ),
          ),
        ),
      ),
    );
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...