【问题标题】:TabBarView inside Sliver with StickyHeader带有 StickyHeader 的 Sliver 内的 TabBarView
【发布时间】:2021-08-03 16:59:38
【问题描述】:

到目前为止,我已经使用 CustomScrollView 和 Sticky Header 制作了这个布局

https://imgur.com/a/Xo4AfAM

我想要实现的是选项卡下方的内容不应滚动,除非有额外的内容可用。此外,滚动后,内容不应位于置顶标题后面。

到目前为止我的代码。

Scaffold(
  backgroundColor: Colors.white,
  extendBodyBehindAppBar: true,
  appBar: AppBar(
    toolbarHeight: getHeight() * (1 / 11),
  ),
  body: Padding(
    padding: getPaddings(),
    child: DefaultTabController(
      length: 3,
      child: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: Container(
              height: getHeight() * (3 / 11),
              color: Colors.blue,
            ),
          ),
          SliverStickyHeader(
            header: Column(
              children: [
                Container(
                  height: getHeight() * (1 / 11),
                  width: double.infinity,
                  color: kPrimaryColor,
                  child: Center(
                    child: Text(
                      "TEXT",
                      style: TextStyle(
                        fontSize: 32,
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ),
                Container(
                  height: getHeight() * (1 / 11),
                  color: kPrimaryColor,
                  child: TabBar(
                    tabs: [
                      Tab(
                        child: Text(
                          'TITLE1',
                          style: TextStyle(color: Colors.black),
                        ),
                      ),
                      Tab(
                        child: Text(
                          'TITLE2',
                          style: TextStyle(color: Colors.black),
                        ),
                      ),
                      Tab(
                        child: Text(
                          'TITLE3',
                          style: TextStyle(color: Colors.black),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            sliver: SliverFillRemaining(
              child: TabBarView(
                children: [
                  Padding(
                    padding: EdgeInsets.symmetric(
                        horizontal: getProportionateScreenWidth(50),
                        vertical: getProportionateScreenHeight(20)),
                    child: Column(
                      children: [
                        RoundedPicture(),
                        FittedBox(
                          child: Text("Hello World",
                              style: TextStyle(
                                  fontWeight: FontWeight.bold,
                                  color: Colors.black,
                                  fontSize: 40)),
                        ),
                        SizedBox(
                          height: getProportionateScreenHeight(20),
                        ),
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            RichText(
                              text: TextSpan(
                                  style: TextStyle(
                                      fontWeight: FontWeight.bold,
                                      color: Colors.black,
                                      fontSize: 20),
                                  text: 'Info1:  ',
                                  children: [
                                    TextSpan(
                                      text: "123",
                                      style: TextStyle(
                                        color: kSecondaryColor,
                                      ),
                                    ),
                                  ]),
                            ),
                            SizedBox(
                              height: getProportionateScreenHeight(20),
                            ),
                            RichText(
                              text: TextSpan(
                                  style: TextStyle(
                                      fontWeight: FontWeight.bold,
                                      color: Colors.black,
                                      fontSize: 20),
                                  text: 'Info2:  ',
                                  children: [
                                    TextSpan(
                                      text: "abcd",
                                      style: TextStyle(
                                        color: kSecondaryColor,
                                      ),
                                    ),
                                  ]),
                            ),
                            SizedBox(
                              height: getProportionateScreenHeight(20),
                            ),
                            RichText(
                              text: TextSpan(
                                  style: TextStyle(
                                      fontWeight: FontWeight.bold,
                                      color: Colors.black,
                                      fontSize: 20),
                                  text: 'Info3:  ',
                                  children: [
                                    TextSpan(
                                      text: "xyz",
                                      style: TextStyle(
                                        color: kSecondaryColor,
                                      ),
                                    ),
                                  ]),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                  Center(
                    child: Text("TITLE2"),
                  ),
                  Center(
                    child: Text("TITLE3"),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    ),
  ),

为了实现所需的布局,我尝试在不同的条子内使用 TabBarView,即 SliverList 和 SliverToBoxAdapter,但它们都导致错误,因为 TabBarView 没有预定义的高度。

【问题讨论】:

    标签: flutter


    【解决方案1】:

    这是我的实现。
    因为没有与大小相关的方法,所以我只是自己设置了一个值。

    • 堆栈
      • 默认选项卡控制器

        • 嵌套滚动视图
          • SliverAppBar

          • SliverPersistentHeader

          • SliverPersistentHeader

          • 标签栏视图

      • 容器 // 用于返回按钮

    import 'package:flutter/material.dart';
    import 'dart:math' as math;
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    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 {
      TabController _tabController;
      @override
      void initState() {
        super.initState();
        _tabController = TabController(initialIndex: 0, length: 3, vsync: this);
      }
    
      double getHeight() {
        return 800;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.white,
          extendBodyBehindAppBar: true,
          body: SafeArea(
            child: Stack(
              children: [
                DefaultTabController(
                  length: 3,
                  child: NestedScrollView(
                    headerSliverBuilder: (context, value) {
                      return [
                        SliverAppBar(
                          expandedHeight: 200.0,
                          flexibleSpace: FlexibleSpaceBar(),
                        ),
                        SliverPersistentHeader(
                          pinned: true,
                          delegate: _SliverAppBarDelegate(
                            minHeight: 90,
                            maxHeight: 90,
                            child: Container(
                              height: getHeight() * (1 / 11),
                              width: double.infinity,
                              color: Colors.green[200],
                              child: Center(
                                child: Text(
                                  "TEXT",
                                  style: TextStyle(
                                    fontSize: 32,
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ),
                        SliverPersistentHeader(
                          pinned: true,
                          delegate: _SliverAppBarDelegate(
                            minHeight: 90,
                            maxHeight: 90,
                            child: Container(
                              color: Colors.green[200],
                              child: TabBar(
                                controller: _tabController,
                                tabs: [
                                  Tab(
                                    child: Text(
                                      'TITLE1',
                                      style: TextStyle(
                                        color: Colors.black,
                                      ),
                                    ),
                                  ),
                                  Tab(
                                    child: Text(
                                      'TITLE2',
                                      style: TextStyle(color: Colors.black),
                                    ),
                                  ),
                                  Tab(
                                    child: Text(
                                      'TITLE3',
                                      style: TextStyle(color: Colors.black),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        ),
                      ];
                    },
                    body: TabBarView(
                      controller: _tabController,
                      children: [
                        SingleChildScrollView(
                          child: Container(
                            padding: EdgeInsets.only(bottom: 600),
                            child: Column(
                              children: [
                                // RoundedPicture(),
                                Icon(
                                  Icons.favorite,
                                  color: Colors.pink,
                                  size: 150.0,
                                  semanticLabel:
                                      'Text to announce in accessibility modes',
                                ),
                                FittedBox(
                                  child: Text("Hello World",
                                      style: TextStyle(
                                          fontWeight: FontWeight.bold,
                                          color: Colors.black,
                                          fontSize: 40)),
                                ),
                                SizedBox(
                                  height: 20,
                                ),
                                Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    RichText(
                                      text: TextSpan(
                                          style: TextStyle(
                                              fontWeight: FontWeight.bold,
                                              color: Colors.black,
                                              fontSize: 20),
                                          text: 'Info1:  ',
                                          children: [
                                            TextSpan(
                                              text: "123",
                                              style: TextStyle(),
                                            ),
                                          ]),
                                    ),
                                    SizedBox(
                                      height: 20,
                                    ),
                                    RichText(
                                      text: TextSpan(
                                          style: TextStyle(
                                              fontWeight: FontWeight.bold,
                                              color: Colors.black,
                                              fontSize: 20),
                                          text: 'Info2:  ',
                                          children: [
                                            TextSpan(
                                              text: "abcd",
                                              style: TextStyle(),
                                            ),
                                          ]),
                                    ),
                                    SizedBox(
                                      height: 20,
                                    ),
                                    RichText(
                                      text: TextSpan(
                                          style: TextStyle(
                                              fontWeight: FontWeight.bold,
                                              color: Colors.black,
                                              fontSize: 20),
                                          text: 'Info3:  ',
                                          children: [
                                            TextSpan(
                                              text: "xyz",
                                              style: TextStyle(),
                                            ),
                                          ]),
                                    ),
                                  ],
                                ),
                              ],
                            ),
                          ),
                        ),
                        SingleChildScrollView(
                          child: Container(
                            padding: EdgeInsets.only(bottom: 600),
                            child: Column(
                              children: [
                                Container(
                                  padding: EdgeInsets.only(bottom: 600),
                                  child: Center(
                                    child: Text("TITLE2"),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                        SingleChildScrollView(
                          child: Container(
                            padding: EdgeInsets.only(bottom: 600),
                            child: Column(
                              children: [
                                Container(
                                  padding: EdgeInsets.only(bottom: 600),
                                  child: Center(
                                    child: Text("TITLE3"),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                Container(
                  height: 90,
                  padding: EdgeInsets.symmetric(horizontal: 15),
                  child: InkWell(
                    onTap: () {},
                    child: Icon(Icons.arrow_back),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
      _SliverAppBarDelegate({
        @required this.minHeight,
        @required this.maxHeight,
        @required this.child,
      });
      final double minHeight;
      final double maxHeight;
      final Widget child;
    
      @override
      double get minExtent => minHeight;
    
      @override
      double get maxExtent => math.max(maxHeight, minHeight);
    
      @override
      Widget build(
          BuildContext context, double shrinkOffset, bool overlapsContent) {
        return SizedBox.expand(child: child);
      }
    
      @override
      bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
        return maxHeight != oldDelegate.maxHeight ||
            minHeight != oldDelegate.minHeight ||
            child != oldDelegate.child;
      }
    }
    
    

    【讨论】:

    • 抱歉回复晚了。您的代码看起来不错,但它也有我面临的相同问题。首先,屏幕不应该滚动,除非有额外的内容可用而没有。其次,即使它确实滚动,除非列表的内容大于可用大小,否则内容至少不应落后于粘性标题。
    • 我改变了实现你想要的。包装 SingleChildScrollView 和 Container 并添加与标题大小一样多的底部填充以用于内容滚动。
    • 我想你没有理解我的担心。检查我用下面的代码制作的这个布局。 imgur.com/NkzJ7vi 第一个标签和第三个标签不滚动,因为没有任何额外的内容。第二个选项卡仅滚动到显示额外内容的范围。它不会一直在持久标头后面。我将滚动物理更改为弹跳,这样您就可以看到发生了什么。如果我将其更改为钳位,则第一个和第三个选项卡根本不会滚动,只有第二个选项卡会滚动。
    • 很抱歉造成误会。你的意思是虽然我改变了,它可以在没有额外内容的情况下滚动,但这不是你想要的。
    • 希望它在没有额外内容的情况下滚动。我的初始布局也在滚动,没有额外的内容。不过,感谢代码,给了我解决问题的新视角和想法。
    【解决方案2】:

    在没有找到可能的解决方案后,我决定实现自己的标签栏视图,而不是使用默认的。该解决方案并不那么花哨,因为选项卡的转换是通过更改当前页码并重建小部件来完成的,但解决了问题。我使用了一个手势检测器来允许用户滑动页面。

              GestureDetector(
               onHorizontalDragEnd: (dragDetails) {
                 if (dragDetails.primaryVelocity != 0) {
                   final int val = dragDetails.primaryVelocity.sign.toInt();
                   if (currentPage - val >= 0 &&
                       currentPage - val < tabController.length)
                     tabController.animateTo(currentPage - val);
                 }
               },
               child: CustomScrollView(
                 shrinkWrap: true,
                 controller: scrollController,
                 slivers: [
                   SliverToBoxAdapter(
                     child: LogoContainer(),
                   ),
                   SliverStickyHeader(
                     header: Column(
                       children: [
                         NameContainer(text: 'TEXT'),
                         Container(
                           height: 60,
                           color: kSecondaryColor,
                           child: TabBar(
                             controller: tabController,
                             tabs: [
                               Tab(
                                 child: Text(
                                   'TITLE1',
                                   style: TextStyle(color: Colors.black),
                                 ),
                               ),
                               Tab(
                                 child: Text(
                                   'TITLE2',
                                   style: TextStyle(color: Colors.black),
                                 ),
                               ),
                               Tab(
                                 child: Text(
                                   'TITLE3',
                                   style: TextStyle(color: Colors.black),
                                 ),
                               ),
                             ],
                           ),
                         ),
                       ],
                     ),
                     sliver: SliverToBoxAdapter(
                       child: ConstrainedBox(
                         constraints:
                             BoxConstraints(minHeight: getHeight() * (6 / 11)),
                         child: [
                           Column(
                             children: [
                               RoundedPicture(),
                               FittedBox(
                                 child: Text(
                                   'HELLO WORLD',
                                   style: TextStyle(
                                       fontWeight: FontWeight.bold,
                                       color: Colors.black,
                                       fontSize: 40),
                                 ),
                               ),
                               SizedBox(
                                 height: getProportionateScreenHeight(20),
                               ),
                               Column(
                                 children: [
                                   ProfileInfoText(
                                     title: 'INFO1',
                                     text: 'abcd',
                                   ),
                                   SizedBox(
                                     height: getProportionateScreenHeight(20),
                                   ),
                                   ProfileInfoText(
                                     title: 'INFO2',
                                     text: '123',
                                   ),
                                   SizedBox(
                                     height: getProportionateScreenHeight(20),
                                   ),
                                   ProfileInfoText(
                                     title: 'INFO3',
                                     text: 'xyz',
                                   ),
                                 ],
                               ),
                             ],
                           ),
                           Center(
                             child: Text("TITLE2"),
                           ),
                           Center(
                             child: Text("TITLE3"),
                           ),
                         ].elementAt(currentPage),
                       ),
                     ),
                   ),
                 ],
               ),
             ),
    

    我还使用滚动控制器在更改选项卡时将列表设置为动画,使其看起来更流畅。

    @override
      void initState() {
        super.initState();
        tabController = TabController(length: 3, vsync: this)
          ..addListener(() async {
            await scrollController.animateTo(
              0,
              duration: Duration(seconds: 1),
              curve: Curves.ease,
            );
            setState(() {
              currentPage = tabController.index;
            });
          });
      }
    

    【讨论】:

      猜你喜欢
      • 2021-01-12
      • 2020-11-27
      • 2019-03-15
      • 2019-01-17
      • 2020-10-10
      • 1970-01-01
      • 2020-01-21
      • 2020-12-15
      • 1970-01-01
      相关资源
      最近更新 更多