如何使用 NestedScrollView 滚动以在 SliverAppBar 上使用堆栈圆形头像?

Posted

技术标签:

【中文标题】如何使用 NestedScrollView 滚动以在 SliverAppBar 上使用堆栈圆形头像?【英文标题】:How to scroll with NestedScrollView to use stack Circle Avatar over SliverAppBar? 【发布时间】:2022-01-15 00:35:23 【问题描述】:

我最近在处理一个Flutter项目,遇到这样的问题。

参见照片 1:这是我的代码:照片和文本应位于 TabBar 小部件和设计中的红色背景(蓝色)之间。

这里你可以看到我的实际代码:

主类

   body: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) 
          return <Widget>[
            PortfoliosliverAppBar(_pages[_tabController.index].item1),
            SliverPersistentHeader(
              delegate: SliverPersistentHeaderDelegateImpl(
                tabBar: TabBar(
                  padding: EdgeInsets.only(top: 15.0),
                  labelColor: Colors.black,
                  indicatorColor: Colors.black,
                  indicator: BoxDecoration(
                      borderRadius: BorderRadius.all(Radius.circular(18)),
                      color: Colors.blue),
                  controller: _tabController,
                  tabs: _pages
                      .map<Tab>((Tuple3 page) => Tab(text: page.item1))
                      .toList(),
                ),
              ),
            ),
          ];
        ,
        body: Container(
          margin: EdgeInsets.only(top: 20.0),
          child: TabBarView(
            controller: _tabController,
            children: _pages.map<Widget>((Tuple3 page) => page.item2).toList(),
          ),
        ),

银级

class SliverPersistentHeaderDelegateImpl extends SliverPersistentHeaderDelegate 
  final TabBar tabBar;
  final Color color;

  const SliverPersistentHeaderDelegateImpl(
    Color color = Colors.transparent,
    @required this.tabBar,
  ) : this.color = color;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) 
    return Container(
      color: color,
      child: tabBar,
    );
  

参见照片 2:这是给定的设计:

my view

the actual UI

非常感谢!

【问题讨论】:

【参考方案1】:

请参考以下代码


class NestedScrollWithTabs extends StatefulWidget 
  const NestedScrollWithTabs(Key key) : super(key: key);

  @override
  _NestedScrollWithTabsState createState() => _NestedScrollWithTabsState();


class _NestedScrollWithTabsState extends State<NestedScrollWithTabs>
    with TickerProviderStateMixin 
  
  var animation;
  var controller;

  @override
  Widget build(BuildContext context) 
    return SafeArea(
      child: Scaffold(
        body: DefaultTabController(
          length: 2,
          child: NestedScrollView(
            physics: NeverScrollableScrollPhysics(),
            headerSliverBuilder: (headerCtx, innnerBoxIsScrolled) 
              if (innnerBoxIsScrolled) 
                /* Animation */
                controller = AnimationController(
                  vsync: this,
                  duration: Duration(
                    seconds: 1,
                  ),
                );
                animation = Tween(
                  begin: 0.0,
                  end: 1.0,
                ).animate(controller);
                /* Animation */
                controller.forward();
              

              return <Widget>[
                SliverAppBar(
                  expandedHeight: ScreenUtil().setHeight(185.0),
                  floating: false,
                  pinned: true,
                  backgroundColor: Colors.white,
                  automaticallyImplyLeading: false,
                  titleSpacing: 0.0,
                  centerTitle: true,
                  elevation: 0.0,
                  leadingWidth: 0.0,
                  title: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                    
                      if (innnerBoxIsScrolled != null &&
                          innnerBoxIsScrolled == true)
                        FadeTransition(
                          opacity: animation,
                          child: Text(
                            "Title",
                            style: TextStyle(
                              color: Colors.black,
                              fontSize: 22,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                     
                    ],
                  ),
                  flexibleSpace: FlexibleSpaceBar(
                    background: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Stack(
                          alignment: Alignment.center,
                          clipBehavior: Clip.none,
                          children: [
                            Image.network(
                              "https://images.pexels.com/photos/10181294/pexels-photo-10181294.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500" ??
                                  "",
                              fit: BoxFit.fitWidth,
                              height: ScreenUtil().setHeight(126.0),
                              width: ScreenUtil().screenWidth,
                              filterQuality: FilterQuality.low,
                              loadingBuilder: (BuildContext context,
                                  Widget child,
                                  ImageChunkEvent loadingProgress) 
                                if (loadingProgress == null) return child;
                                return Container(
                                  height: ScreenUtil().setHeight(126.0),
                                  width: ScreenUtil().screenWidth,
                                  color: Colors.grey,
                                );
                              ,
                              errorBuilder: (context, error, stackTrace) 
                                return SizedBox(
                                  height: ScreenUtil().setHeight(126.0),
                                  width: ScreenUtil().screenWidth,
                                  child: Container(
                                    width: ScreenUtil().screenWidth,
                                  ),
                                );
                              ,
                            ),
                            Positioned(
                              top: ScreenUtil().setHeight(92.0),
                              // left: ScreenUtil().setWidth(20.0),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  CircleAvatar(
                                    backgroundColor: Colors.transparent,
                                    radius: 30.0,
                                    child: ClipRRect(
                                      borderRadius: BorderRadius.circular(
                                        45.0,
                                      ),
                                      child: Image.network(
                                        "https://images.pexels.com/photos/10181294/pexels-photo-10181294.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500" ??
                                            "",
                                        fit: BoxFit.fill,
                                        height: ScreenUtil().setHeight(72.0),
                                        width: ScreenUtil().screenWidth,
                                        filterQuality: FilterQuality.low,
                                        loadingBuilder: (BuildContext context,
                                            Widget child,
                                            ImageChunkEvent loadingProgress) 
                                          if (loadingProgress == null)
                                            return child;
                                          return Container(
                                            height:
                                                ScreenUtil().setHeight(72.0),
                                            width: ScreenUtil().screenWidth,
                                            color: Colors.grey,
                                          );
                                        ,
                                        errorBuilder:
                                            (context, error, stackTrace) 
                                          return SizedBox(
                                            height:
                                                ScreenUtil().setHeight(72.0),
                                            width: ScreenUtil().screenWidth,
                                            child: Container(
                                              width: ScreenUtil().screenWidth,
                                            ),
                                          );
                                        ,
                                      ),
                                    ),
                                  ),
                                  Text("Name"),
                                  Text("Place"),
                                ],
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
                SliverOverlapAbsorber(
                  handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                      headerCtx),
                  sliver: SliverPersistentHeader(
                    delegate: SliverAppBarDelegate(TabBar(
                      labelColor: Colors.blue,
                      unselectedLabelColor: Colors.black,
                      labelStyle: TextStyle(
                        fontSize: 15.0,
                      ),
                      unselectedLabelStyle: TextStyle(
                        fontSize: 15.0,
                      ),
                      labelPadding: EdgeInsets.zero,
                      indicatorColor: Colors.blue,
                      indicatorPadding: EdgeInsets.zero,
                      physics: NeverScrollableScrollPhysics(),
                      tabs: [
                        Tab(
                          text: "Tab 1",
                        ),
                        Tab(
                          text: "Tab 2",
                        ),
                      ],
                    )),
                    pinned: false,
                  ),
                ),
              ];
            ,
            body: TabBarView(
              children: [
                /* Tab 1 */
                Container(
                  color: Colors.white,
                  child: ListView.builder(
                    itemCount: 100,
                    physics: NeverScrollableScrollPhysics(),
                    shrinkWrap: true,
                    itemBuilder: (BuildContext context, int index) 
                      return Padding(
                        padding: const EdgeInsets.all(4.0),
                        child: Text("Index value: $index"),
                      );
                    ,
                  ),
                ),
                /* Tab 2 */
                Container(
                  color: Colors.white,
                  child: ListView.builder(
                    itemCount: 10,
                    physics: NeverScrollableScrollPhysics(),
                    shrinkWrap: true,
                    itemBuilder: (BuildContext context, int index) 
                      return Padding(
                        padding: const EdgeInsets.all(4.0),
                        child: Text("Index value of Tab 2: $index"),
                      );
                    ,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  


class SliverAppBarDelegate extends SliverPersistentHeaderDelegate 
  SliverAppBarDelegate(this.tabBars);

  final TabBar tabBars;

  @override
  double get minExtent => 60.0;

  @override
  double get maxExtent => 60.0;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) 
    // shinkOffsetPerValue.value = shrinkOffset;
    return new Container(
      color: Colors.white,
      child: Column(
        children: [
          tabBars,
        ],
      ),
    );
  

  @override
  bool shouldRebuild(SliverAppBarDelegate oldDelegate) 
    return false;
  


【讨论】:

你能补充一点关于你做了什么以及为什么这样做的信息吗,因为这会很有帮助。

以上是关于如何使用 NestedScrollView 滚动以在 SliverAppBar 上使用堆栈圆形头像?的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式滚动到 NestedScrollView 的顶部

如何使用里面的 ListView 滚动 NestedScrollView?

如何在不使用 NestedScrollView 的情况下滚动 ListView?

滚动 NestedScrollView 时如何获取 recyclerview 的当前项目位置?

无法滚动到 NestedScrollView 内的 RecyclerView 中的项目

LinearLayout 和 NestedScrollView 内的 RecyclerView - 如何滚动到某个位置的项目顶部?