peng8350 / flutter_pulltorefresh

a widget provided to the flutter scroll component drop-down refresh and pull up load.
MIT License
2.71k stars 722 forks source link

如何嵌套使用NestedScrollView和TabBarView? #497

Open iAllenC opened 3 years ago

iAllenC commented 3 years ago

结构是SmartRefresher--NestedScrollView->SliverAppBar+TabBarView->GridView。 页面显示没有问题,但是滑动的时候无法显示下拉刷新 把刷新放到GridView上可以解决,但是因为SliverAppBar上的部分内容也是跟随刷新的,所以这个下拉刷新还是希望能放到外面来。不知道是我的用法不对还是怎样?代码如下:

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';

class NestedPageRoute extends StatefulWidget { @override State createState() { return _NestedPageState(); } }

class _NestedPageState extends State with SingleTickerProviderStateMixin { RefreshController _refreshController = RefreshController(initialRefresh: true); ScrollController _scrollviewController = ScrollController(); TabController? _tabController;

@override void initState() { // TODO: implement initState super.initState(); _tabController = TabController(length: 4, vsync: this); }

Widget _buildFooter() { return CustomFooter(builder: (BuildContext context, LoadStatus? mode) { Widget body; if (mode == LoadStatus.idle) { body = Text("上拉加载"); } else if (mode == LoadStatus.loading) { body = CupertinoActivityIndicator(); } else if (mode == LoadStatus.failed) { body = Text("加载失败!点击重试!"); } else if (mode == LoadStatus.canLoading) { body = Text("松手,加载更多!"); } else { body = Text("没有更多数据了!"); } return Container( height: 55.0, child: Center(child: body), ); }); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Title"),), body: SmartRefresher( controller: _refreshController, scrollController: _scrollviewController, enablePullDown: true, enablePullUp: true, header: WaterDropHeader( refresh: CupertinoActivityIndicator(), ), footer: _buildFooter(), onRefresh: _onRefresh, onLoading: _onLoading, child: NestedScrollView( controller: _scrollviewController, headerSliverBuilder: (context, boxIsScrolled) { return [ SliverAppBar( pinned: true, floating: true, elevation: 0.5, forceElevated: true, //backgroundColor: Colors.grey, expandedHeight: 240, flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.pin, //视差效果 background: Container( //color: Colors.grey, child: Column( children: [ Container( height: 190, color: Colors.blue, ) ], ), ), ), bottom: TabBar(controller: _tabController, tabs: [ Tab( text: "首页", ), Tab( text: "消息", ), Tab( text: "购物", ), Tab( text: "我的", ) ]), ), ]; }, body: TabBarView(controller: _tabController, children: [ ListView.builder(itemBuilder: (context, index) { return Center( child: Text(index.toString()), ); }, itemCount: 50,shrinkWrap: true, physics: NeverScrollableScrollPhysics(),), Center( child: Text("two"), ), Center( child: Text("three"), ), Center( child: Text("four"), ), ])), ) ); }

void _onRefresh() async { await Future.delayed(Duration(seconds: 2)); _refreshController.refreshCompleted(); setState(() {}); }

void _onLoading() async { await Future.delayed(Duration(seconds: 2)); _refreshController.loadComplete(); setState(() {}); }

}

houziruinb87 commented 3 years ago

作者的这个库不支持这种方式,还有一个比较常用的flutter的刷新库flutter_easyrefresh.和作者的实现思路一样,都是用customScrollView包裹住咱们的布局,然后在头部insert一个header,底部add一个footer. 所以这两个刷新库,天然就不支持包裹NestScrollView. 如果想实现你的这个业务,有两个思路: 1.还使用这些第三方的刷新库,你自己的布局用CustomScrollView,然后自己定义ScrollPositon和ScrollController.这种方式稍微复杂一些,可以参考一下美团外卖的那个demo. 我使用的是flutter_easyrefresh这个库,在模拟NestedScrollView实现了自定义的scrollPositon后,实现了和NestedScrollView相似的滑动模式.但是嵌套入下拉刷新库内会出现一些滑动bug,目前还没找到好的解决方案.

2.自己做一个刷新控件,可以包裹NestScrollView,通过外层包裹ScrollNotification然后监听滑动在顶部还是底部.目前正在看这些刷新库的源码实现. @peng8350 作者看一下我说的对不对

houziruinb87 commented 3 years ago

需求:如同咸鱼首页(推荐那个页) 1.整个页面支持下拉刷新,上拉加载更多.(下拉上拉时,整个页面一起滑动) 2.整个页面中含有以下内容模块(纵向排列): 横向列表(固定数量),轮播图,可以跟随滑动并吸顶的TabBar,与TabBar绑定的TabBarView. 3.实现思路.我目前的实现思路有两种

第一种思路: A:使用第三方刷新库(flutter_easyrefresh/pull_to_refresh)为最外层布局 自己的业务布局使用CustomScrollView 横向列表(固定数量)和轮播图使用SliverList包裹. 可以跟随滑动并吸顶的TabBar使用SliverPersistentHeader 与TabBar绑定的TabBarView使用SliverFillRemaining包裹. 问题就出现在这个TabBarView, 我之前的想法是TabBarView里的每一个Page中会用到ListView,我把这些ListView设置成shrinkWrap为True,然后physics设置成NeverScrollableScrollPhysics.(想禁用掉ListView自己的滑动并且充满父布局). 如上操作之后,使用SliverFillRemaining包裹后,SliverFillRemaining里的TabBarView并没有被ListView撑满,整个SliverFillRemaing的布局高度始终是一个固定的数值.

第二种思路: 自己的业务布局使用NestedScrollView 横向列表(固定数量)和轮播图使用SliverList包裹. 可以跟随滑动并吸顶的TabBar使用SliverPersistentHeader. 将以上两个子布局放入NestedScrollView的headerSliverBuilder中,将TabBarView放入NestedScrollView的body中. 不禁用TabBarView中ListView的滚动. 这样整个页面滑动起来不会有问题. 问题出现在 将以上的NestedScrollView放入第三方刷新库(flutter_easyrefresh/pull_to_refresh)中后会出现问题.看了一下第三方库的源码,是把业务布局放入CustomScrollView中实现的.但是NestedScrollView无法作为一个sliver插入到CustomScrollView中.导致无法下拉刷新加载更多. 综上,想请教一下咸鱼这个页面的布局实现思路是怎样的.期待回复!感谢!

yingzhong27 commented 3 years ago

@houziruinb87 请问你解决了你说的这个问题了吗 我遇到了和你差不多的问题

yingzhong27 commented 3 years ago

结构是SmartRefresher--NestedScrollView->SliverAppBar+TabBarView->GridView。 页面显示没有问题,但是滑动的时候无法显示下拉刷新 把刷新放到GridView上可以解决,但是因为SliverAppBar上的部分内容也是跟随刷新的,所以这个下拉刷新还是希望能放到外面来。不知道是我的用法不对还是怎样?代码如下:

import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';

class NestedPageRoute extends StatefulWidget { @override State createState() { return _NestedPageState(); } }

class _NestedPageState extends State with SingleTickerProviderStateMixin { RefreshController _refreshController = RefreshController(initialRefresh: true); ScrollController _scrollviewController = ScrollController(); TabController? _tabController;

@override void initState() { // TODO: implement initState super.initState(); _tabController = TabController(length: 4, vsync: this); }

Widget _buildFooter() { return CustomFooter(builder: (BuildContext context, LoadStatus? mode) { Widget body; if (mode == LoadStatus.idle) { body = Text("上拉加载"); } else if (mode == LoadStatus.loading) { body = CupertinoActivityIndicator(); } else if (mode == LoadStatus.failed) { body = Text("加载失败!点击重试!"); } else if (mode == LoadStatus.canLoading) { body = Text("松手,加载更多!"); } else { body = Text("没有更多数据了!"); } return Container( height: 55.0, child: Center(child: body), ); }); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Title"),), body: SmartRefresher( controller: _refreshController, scrollController: _scrollviewController, enablePullDown: true, enablePullUp: true, header: WaterDropHeader( refresh: CupertinoActivityIndicator(), ), footer: _buildFooter(), onRefresh: _onRefresh, onLoading: _onLoading, child: NestedScrollView( controller: _scrollviewController, headerSliverBuilder: (context, boxIsScrolled) { return [ SliverAppBar( pinned: true, floating: true, elevation: 0.5, forceElevated: true, //backgroundColor: Colors.grey, expandedHeight: 240, flexibleSpace: FlexibleSpaceBar( collapseMode: CollapseMode.pin, //视差效果 background: Container( //color: Colors.grey, child: Column( children: [ Container( height: 190, color: Colors.blue, ) ], ), ), ), bottom: TabBar(controller: _tabController, tabs: [ Tab( text: "首页", ), Tab( text: "消息", ), Tab( text: "购物", ), Tab( text: "我的", ) ]), ), ]; }, body: TabBarView(controller: _tabController, children: [ ListView.builder(itemBuilder: (context, index) { return Center( child: Text(index.toString()), ); }, itemCount: 50,shrinkWrap: true, physics: NeverScrollableScrollPhysics(),), Center( child: Text("two"), ), Center( child: Text("three"), ), Center( child: Text("four"), ), ])), ) ); }

void _onRefresh() async { await Future.delayed(Duration(seconds: 2)); _refreshController.refreshCompleted(); setState(() {}); }

void _onLoading() async { await Future.delayed(Duration(seconds: 2)); _refreshController.loadComplete(); setState(() {}); }

}

请问解决了吗?

qingen

yingzhong27 commented 3 years ago

class NestedPageRoute extends StatefulWidget { @override State createState() { return _NestedPageState(); } }

class _NestedPageState extends State with SingleTickerProviderStateMixin { refresh.RefreshController _refreshController = refresh.RefreshController(initialRefresh: true); ScrollController _scrollviewController = ScrollController(); TabController _tabController;

@override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); } List list1 = [1,2,3,4,5,6,7,8,9,]; @override Widget build(BuildContext context) {

return Scaffold(
  appBar: AppBar(
    title: Text("Title"),
  ),
  body: RefreshIndicator(
      notificationPredicate: (notifation) {
        // 返回true即可
        return true;
      },
      onRefresh: () {
        return Future.delayed(Duration(seconds: 2), () {
          print("我是调用的外部刷新0");
          return true;
        });
      },
      child: NestedScrollView(
          controller: _scrollviewController,
          headerSliverBuilder: (context, boxIsScrolled) {
            return [
              SliverAppBar(
                pinned: true,
                floating: true,
                elevation: 0.5,
                forceElevated: true,
                expandedHeight: 240,
                flexibleSpace: FlexibleSpaceBar(
                  collapseMode: CollapseMode.pin, //视差效果
                  background: Container(
                    child: Column(
                      children: [
                        Container(
                          height: 190,
                          color: Colors.blue,
                        )
                      ],
                    ),
                  ),
                ),
                bottom: TabBar(controller: _tabController, tabs: [
                  Tab(
                    text: "首页",
                  ),
                  Tab(
                    text: "消息",
                  ),
                  Tab(
                    text: "购物",
                  ),
                  Tab(
                    text: "我的",
                  )
                ]),
              ),
            ];
          },
          body: TabBarView(controller: _tabController, children: [
            refresh.SmartRefresher(
              controller: _refreshController,
              onRefresh: _onRefresh,
              onLoading: _onLoading,
              enablePullDown: false,
              enablePullUp: true,
              child: ListView.builder(
                itemBuilder: (context, index) {
                  return Center(
                    child: Text(list1[index].toString()),
                  );
                },
                itemCount: list1.length,
                // shrinkWrap: true,
                // physics: NeverScrollableScrollPhysics(),
              ),
            ),
            Center(
              child: Text("two"),
            ),
            Center(
              child: Text("three"),
            ),
            Center(
              child: Text("four"),
            ),
          ]))),
  // )
);

}

void _onRefresh() async { await Future.delayed(Duration(seconds: 2)); _refreshController.refreshCompleted(); setState(() { print("我是调用的内部刷新1"); }); }

void _onLoading() async { await Future.delayed(Duration(seconds: 2)); setState(() { List list2 = [10,11,12,13,14,15,16,17,18,19,20]; list1.addAll(list2); print("我是调用的内部加载1"); }); _refreshController.loadComplete(); } }

yingzhong27 commented 3 years ago

试试吧 也许是你要的效果

ghost commented 3 years ago

不知道这个是不是你要的效果,你可以看看

evanwsu commented 2 years ago

需求:如同咸鱼首页(推荐那个页) 1.整个页面支持下拉刷新,上拉加载更多.(下拉上拉时,整个页面一起滑动) 2.整个页面中含有以下内容模块(纵向排列): 横向列表(固定数量),轮播图,可以跟随滑动并吸顶的TabBar,与TabBar绑定的TabBarView. 3.实现思路.我目前的实现思路有两种

第一种思路: A:使用第三方刷新库(flutter_easyrefresh/pull_to_refresh)为最外层布局 自己的业务布局使用CustomScrollView 横向列表(固定数量)和轮播图使用SliverList包裹. 可以跟随滑动并吸顶的TabBar使用SliverPersistentHeader 与TabBar绑定的TabBarView使用SliverFillRemaining包裹. 问题就出现在这个TabBarView, 我之前的想法是TabBarView里的每一个Page中会用到ListView,我把这些ListView设置成shrinkWrap为True,然后physics设置成NeverScrollableScrollPhysics.(想禁用掉ListView自己的滑动并且充满父布局). 如上操作之后,使用SliverFillRemaining包裹后,SliverFillRemaining里的TabBarView并没有被ListView撑满,整个SliverFillRemaing的布局高度始终是一个固定的数值.

第二种思路: 自己的业务布局使用NestedScrollView 横向列表(固定数量)和轮播图使用SliverList包裹. 可以跟随滑动并吸顶的TabBar使用SliverPersistentHeader. 将以上两个子布局放入NestedScrollView的headerSliverBuilder中,将TabBarView放入NestedScrollView的body中. 不禁用TabBarView中ListView的滚动. 这样整个页面滑动起来不会有问题. 问题出现在 将以上的NestedScrollView放入第三方刷新库(flutter_easyrefresh/pull_to_refresh)中后会出现问题.看了一下第三方库的源码,是把业务布局放入CustomScrollView中实现的.但是NestedScrollView无法作为一个sliver插入到CustomScrollView中.导致无法下拉刷新加载更多. 综上,想请教一下咸鱼这个页面的布局实现思路是怎样的.期待回复!感谢!

这两种思路我都尝试过,基本和你想的差不多,都没法满足需求。 第一种思路:SliverFillRemaining 可以理解为Expanded一个ScrollView高度,ListView禁滑后,底下部分内容无法查看,可视窗口就是Expanded的高度。这样可以下拉刷新。 第二种思路:使用NestedScrollView滑动没有问题,不能下拉刷新,即使我给NestedScrollView添加了header。感觉ListView区域的滑动只是通过controller设置给NestedScrollView,不能触发下拉刷新。 需要实现这种效果,可能要自定义NestedScrollView代码了。