【问题标题】:Flutter - How to make a row to stay at the top of screen when scrolledFlutter - 滚动时如何使一行保持在屏幕顶部
【发布时间】:2020-10-01 18:18:09
【问题描述】:

我正在尝试实现这样的应用栏

向下滚动时,我需要单独隐藏搜索栏并将行和选项卡固定在设备顶部。就像

当我们向下滚动时,需要显示所有三行。

使用带有底部属性的 SliverAppBar 选项卡在滚动时被放置和固定,但它上方的一行应该固定在选项卡栏上方的顶部。由于底部属性中的preferedSizeWidget,我无法添加带有行和标签栏的列。灵活的空格键也隐藏在 appbar 中,所以我不能使用它。有谁知道如何使这种布局变得扑朔迷离。

【问题讨论】:

标签: flutter dart flutter-animation


【解决方案1】:

您可以创建自己的 SliverAppBar,也可以将它们分成 2 个项目,一个 SliverAppBar 和一个 SliverPersistentHeader

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home>
    with SingleTickerProviderStateMixin {
  TabController controller;
  TextEditingController textController = TextEditingController();

  @override
  void initState() {
    super.initState();
    controller = TabController(
      length: 3,
      vsync: this,
    );
  }

  @override
  void dispose(){
    super.dispose();
    controller.dispose();
    textController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            leading: const Icon(Icons.menu),
            title: TextField(
              controller: textController,
              textInputAction: TextInputAction.search,
              decoration: InputDecoration(
                  isDense: true,
                  hintText: 'Search Bar',
                  hintStyle: TextStyle(color: Colors.black.withOpacity(.5), fontSize: 16),
                  border: InputBorder.none
                )
            ),
            snap: true,
            floating: true,
            actions: [
              IconButton(
                icon: const Icon(Icons.search),
                onPressed: () => print('searching for: ${textController.text}'),
              )
            ]
          ),
          //This is Where you create the row and your tabBar
          SliverPersistentHeader(
            delegate: MyHeader(
              top: Row(
                children: [
                  for(int i = 0; i < 4; i++)
                    Expanded(
                      child: OutlineButton(
                        child: Text('button $i'),
                        onPressed: () => print('button $i pressed'),
                      )
                    )
                ]
              ),
              bottom: TabBar(
                indicatorColor: Colors.white,
                tabs: [
                  Tab(text: 'Tab 1'),
                  Tab(text: 'Tab 2'),
                  Tab(text: 'Tab 3'),
                ],
                controller: controller,
             ),
            ),
            pinned: true,
          ),
          SliverFillRemaining(
            child: TabBarView(
              controller: controller,
              children: <Widget>[
                Center(child: Text("Tab one")),
                Center(child: Text("Tab two")),
                Center(child: Text("Tab three")),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

//Your class should extend SliverPersistentHeaderDelegate to use
class MyHeader extends SliverPersistentHeaderDelegate {
  final TabBar bottom;
  final Widget top;

  MyHeader({this.bottom, this.top});


  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      color: Theme.of(context).accentColor,
      height: math.max(minExtent, maxExtent - shrinkOffset),
      child: Column(
       mainAxisSize: MainAxisSize.min,
       children: [
         if(top != null)
         SizedBox(
           height: kToolbarHeight,
           child: top
         ),
         if(bottom != null)
         bottom
       ]
      )
    );
  }

  /*
   kToolbarHeight = 56.0, you override the max and min extent with the height of a
   normal toolBar plus the height of the tabBar.preferredSize
   so you can fit your row and your tabBar, you give them the same value so it 
   shouldn't shrink when scrolling
  */

  @override
  double get maxExtent => kToolbarHeight + bottom.preferredSize.height;

  @override
  double get minExtent => kToolbarHeight + bottom.preferredSize.height;

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

更新

NestedScollView 让您拥有 2 个 ScrollView,因此您可以使用外部控制内部滚动(就像您想要使用 TabBar 一样)

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

class _HomePageState extends State<HomePage> {
  TextEditingController textController = TextEditingController();
  List<String> _tabs = ['Tab 1', 'Tab 2', 'Tab 3']; 
  // Your tabs, or you can ignore this and build your list
  // on TabBar and the TabView like my previous example.
  // I don't create a TabController now because I wrap the whole widget with a DefaultTabController

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    textController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: DefaultTabController(
      length: _tabs.length, // This is the number of tabs.
      child: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
          return <Widget>[
            SliverOverlapAbsorber(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
              sliver: SliverAppBar(
                elevation: 0.0,
                leading: const Icon(Icons.menu),
                title: TextField(
                  controller: textController,
                  textInputAction: TextInputAction.search,
                  decoration: InputDecoration(
                    isDense: true,
                    hintText: 'Search Bar',
                    hintStyle: TextStyle(
                      color: Colors.black.withOpacity(.5),
                      fontSize: 16),
                    border: InputBorder.none)
                ),
                snap: true,
                floating: true,
                actions: [
                  IconButton(
                    icon: const Icon(Icons.search),
                    onPressed: () => print('searching for: ${textController.text}'),
                  )
                ]
              ),
            ),
            SliverPersistentHeader(
              delegate: MyHeader(
                top: Row(children: [
                  for (int i = 0; i < 4; i++)
                    Expanded(
                        child: OutlineButton(
                      child: Text('button $i'),
                      onPressed: () => print('button $i pressed'),
                    ))
                ]),
                bottom: TabBar(
                  indicatorColor: Colors.white,
                  tabs: _tabs.map((String name) => Tab(text: name)).toList(),
                ),
              ),
              pinned: true,
            ),
          ];
        },
        body: TabBarView(
          children: _tabs.map((String name) {
            return SafeArea(
              child: Builder(
                // This Builder is needed to provide a BuildContext that is
                // "inside" the NestedScrollView, so that
                // sliverOverlapAbsorberHandleFor() can find the
                // NestedScrollView.
                // You can ignore it if you're going to build your
                // widgets in another Stateless/Stateful class.
                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),
                        sliver: SliverFixedExtentList(
                          itemExtent: 48.0,
                          delegate: SliverChildBuilderDelegate(
                            (BuildContext context, int index) {
                              return ListTile(
                                title: Text('Item $index'),
                                onTap: () => print('$name at index $index'),
                              );
                            },
                            childCount: 30,
                          ),
                        ),
                      ),
                    ],
                  );
                },
              ),
            );
          }).toList(),
        ),
      ),
    ));
  }
}

【讨论】:

  • 感谢您的回答,但选项卡视图无法与选项卡下方的 SliverList() 一起使用,有没有办法让条子呈现卡片列表。 SliverFillRemaining 仅适用于可用高度内,不适用于滚动条。 SliverLists() 工作正常,但我无法将其分配给 tabView。你能帮忙@EdwynZn
  • 在这种情况下,您将需要使用 NestedScrollView,因此您可以在外部 ScrollView 中使用 SlvierAppBar 和 PersistentHeader,在每个 TabView 上使用 CustomScrollView,检查更新后的答案
【解决方案2】:

请试试这个。

body: Container(
  child: Column(
    children: <Widget>[
      Container(
        // Here will be your AppBar/Any Widget.
       ),
      Expanded(
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              // All your scroll views
              Container(),
              Container(),
            ],
          ),
        ),
      ),
    ],
  ),
),

【讨论】:

  • 这还很遥远,库马尔
【解决方案3】:
import 'dart:io';
import 'package:flutter/material.dart';


void main() {
  runApp(MyApp());
}


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primaryColor: Colors.white,
      ),
      home: NewsScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class NewsScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _NewsScreenState();
}

class _NewsScreenState extends State<NewsScreen> {
  final List<String> _tabs = <String>[
    "Featured",
    "Popular",
    "Latest",
  ];

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Scaffold(
        body: DefaultTabController(
          length: _tabs.length,
          child: NestedScrollView(
            headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
              return <Widget>[
                SliverOverlapAbsorber(
                  handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  sliver: SliverSafeArea(
                    top: false,
                    bottom: Platform.isIOS ? false : true,
                    sliver: SliverAppBar(
                      title: Text('Tab Demo'),
                      elevation: 0.0,
                      floating: true,
                      pinned: true,
                      snap: true,
                      forceElevated: innerBoxIsScrolled,
                      bottom: TabBar(
                        tabs: _tabs.map((String name) => Tab(text: name)).toList(),
                      ),
                    ),
                  ),

                ),

              ];
            },
            body: TabBarView(
              children: [
                Icon(Icons.directions_car),
                Icon(Icons.directions_transit),
                Icon(Icons.directions_bike),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-16
    • 2012-06-28
    • 2015-02-26
    • 1970-01-01
    • 2021-04-05
    • 2021-07-17
    • 2019-08-08
    相关资源
    最近更新 更多