Flutter TabBar and SliverAppBar that hides when you scroll down

问题: I am trying to create an app with a top application bar and a tab bar below. When you scroll down, the bar should hide by moving off the screen (but tabs should stay), and...

问题:

I am trying to create an app with a top application bar and a tab bar below. When you scroll down, the bar should hide by moving off the screen (but tabs should stay), and when you scroll back up, the application bar should show again. This behaviour can be seen in WhatsApp. Please see this video for a demonstration. (Taken from Material.io). This is a similar behaviour, although the app bar and tab bar are hidden on scroll, so it is not exactly the behaviour I am looking for.

I have been able to achieve the autohiding, however, there are a few issues:

  1. I have to set the snap of the SliverAppBar to true. Without this, the application bar will not show when I scroll back up.

    Although this is works, it is not the behaviour I am looking for. I want the application bar to show smoothly (similar to WhatsApp) rather than coming into view even if you scroll very little.

  2. When I scroll down and change tabs, a little bit of the content is cut out of view.

    Below is a GIF showing the behaviour:

    GIF demonstrating output

    (See the part when I scroll down on the listView (tab1), then move back to tab2)

Here is the code for the 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
      ),
    ),
  ),
),

In case it is needed, the full code is in this GitHub repository. main.dart is here.

I also found this related question: Hide Appbar on Scroll Flutter?. However, it did not provide the solution. The same problems persist, and when you scroll up, the SliverAppBar will not show. (So snap: true is required)

I also found this issue on Flutter's GitHub. (Edit: someone commented that they are waiting for the Flutter team to fix this. Is there a possibility that there is no solution?)

This is the output of flutter doctor -v: Pastebin. Certain issues are found, but from what I have learned, they should not have an impact.


回答1:

--- EDIT 1 --

Alright so I threw together something quick for you. I followed this article (written by Emily Fortuna who is one of the main Flutter devs) to better understand Slivers.

Medium: Slivers, Demystified

But then found this Youtube video that basically used your code so I opted for this one, rather than try to figure out every small detail about Slivers.

Youtube: Using Tab and Scroll Controllers and the NestedScrollView in Dart's Flutter Framework

Turns out you were on the right track with your code. You can use SliverAppBar within NestedScrollView (this wasn't the case last time I tried) but I made a few changes. That I will explain after my code:

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(),
                  ),
              ],
            ),
      ),
    );

  }
}

Alright so on to the explanation.

  1. Use a StatefulWidget

    Most widgets in Flutter are going to be stateful but it depends on the situation. I think in this case it is better because you are using a ListView which could change as users add or erase conversations/chats.

  2. SafeArea because this widget is great.

    Go read about it on Flutter Docs: SafeArea

  3. The Controllers

    I think this was the big issue at first but maybe it was something else. But you should usually make your own controllers if you are dealing with custom behavior in Flutter. So I made the _tabController and the _scrollViewController (I don't think I got every bit of functionality out of them, i.e. keeping track of scroll positions between tabs, but they work for the basics). The tab controller that you use for the TabBar and the TabView should be the same.

  4. The Material Widget before the ListTile

    You probably would have found this out sooner or later but the ListTile widget is a Material widget and therefore requires a "Material ancestor widget" according to the output I got while trying to render it at first. So I saved you a tiny headache with that. I think it is because I didn't use a Scaffold. (Just keep this in mind when you use Material widgets without Material ancestor widgets)

Hope this helps you get started, if you need any assistance with it just message me or add me to your Github repo and I'll see what I can do.


--- ORIGINAL ---

I answered you on Reddit as well, hopefully you see one of these two soon.

SliverAppBar Info

The key properties you want to have with the SliverAppBar are:

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.

All this came from the Flutter SliverAppBar Docs. They have lots of animated examples with different combos of floating, pinned and snap.

So for yours the following should work:

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 with SliverAppBar

To answer the underlying question of the NestedScrollView. According to the docs (same as above) a SliverAppBar is:

A material design app bar that integrates with a CustomScrollView.

Therefore you cannot use a NestedScrollView you need to use a CustomScrollView. This is the intended use of Sliver classes but they can be used in the NestedScrollView Check out the docs.

  • 发表于 2019-03-18 09:28
  • 阅读 ( 3015 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除