Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

多个TabPage,如何在页面最外层支持上拉下拉刷新,而不是在每个TabPage里边 #508

Open
houziruinb87 opened this issue Sep 1, 2021 · 18 comments

Comments

@houziruinb87
Copy link

houziruinb87 commented Sep 1, 2021

1.看了作者的NestedScrollViewPage示例,NestedScrollView-》NestedScrollViewInnerScrollPositionKeyWidget-》EasyRefresh。
这样的结构实现了每个TabPage的上拉下拉刷新。
2.目前我遇到的问题是需要在页面最外层支持上拉下拉刷新,而不是在每个TabPage里边。
3.看过之前作者的介绍,不支持在easyRefresh里边嵌入NestedScrollView,请问这样的效果如何实现呢,如下图。
image
4.目前我得思路是这样的,EasyRefresh-》SliverFillRemaining-》TabBarView-》List。
但是用这种方式,TabBarView总是无法被List撑满,而且还要自己处理List和外层easyRefresh的滑动冲突。
如何吧TabBarView塞进Sliver的全家桶里去呢,难道只能使用NestedScrollView+TabView这种组合么- - 。
请问大神有什么好的解决办法么,或者提供一下思路,万分感谢!
image

@maxwell-nc
Copy link

maxwell-nc commented Sep 7, 2021

个人分析了一下,这个海鲜市场,应该是通过外层整体一个widget下拉刷新,
吸顶并不是nest scrollview,而是用滚动监听位置,然后用一个widget盖在上面,看上去像吸顶一样。
比如一开始那个位置上滑就立刻显示一个header盖住,中间那个就是到了的位置就再盖一个 tabbar上去固定

@clycheng2015
Copy link

+1,有同样的需求,如果采用@maxwell-nc 的方案,切换时会比较明显的交替感!

@gujintao1900
Copy link

gujintao1900 commented Jan 6, 2022

海鲜这个,你自己看的话,子tab的左右手势初始禁止是的,只有子tab吸顶后才允许左右切换

@Jayshanx
Copy link

Jayshanx commented Feb 8, 2022

+1 也有同样的需求,不知道要怎么实现

@Jayshanx
Copy link

Jayshanx commented Feb 8, 2022

海鲜这个,你自己看的话,子tab的左右手势初始禁止是的,只有子tab吸顶后才允许左右切换

初始禁止可能是为了防止和顶部大tab滑动冲突吧

@18363853135
Copy link

+1 , 原生的很多ui都是在最外层刷新

@xuelongqy
Copy link
Owner

这个布局我有时间试试看。v3已发布,请及时更新

@xianyunyehexjy
Copy link

1657761473624

`
EasyRefresh.builder(
onRefresh: () async {
....
return IndicatorResult.success;
},
onLoad: () async {
....
},
childBuilder: (context, physics) {
return ListView(
physics: physics,
);
},
)

`

用这种的就可以解决

@xuelongqy
Copy link
Owner

v3.0.3已发布,添加对NestedScrollView的支持,可参考TabBarViewPage。但我使用Flutter无法完全还原闲鱼的设计,他们的首页看起来貌似是原生写的,后续可以继续一起研究

@Leheih
Copy link

Leheih commented Aug 1, 2022

TabBarViewPage中headerSliverBuilder中子Widget高度大一些,下拉刷新页面会闪烁(当前页面被顶下去了)。不知道具体原因是啥,请大佬解惑。
image

@xuelongqy
Copy link
Owner

TabBarViewPage中headerSliverBuilder中子Widget高度大一些,下拉刷新页面会闪烁(当前页面被顶下去了)。不知道具体原因是啥,请大佬解惑。 image

下个版本会改进

@xuelongqy
Copy link
Owner

@Leheih 3.0.4+1已修复

@EsZhangHome
Copy link

1.看了作者的NestedScrollViewPage示例,NestedScrollView-》NestedScrollViewInnerScrollPositionKeyWidget-》EasyRefresh。 这样的结构实现了每个TabPage的上拉下拉刷新。 2.目前我遇到的问题是需要在页面最外层支持上拉下拉刷新,而不是在每个TabPage里边。 3.看过之前作者的介绍,不支持在easyRefresh里边嵌入NestedScrollView,请问这样的效果如何实现呢,如下图。 image 4.目前我得思路是这样的,EasyRefresh-》SliverFillRemaining-》TabBarView-》List。 但是用这种方式,TabBarView总是无法被List撑满,而且还要自己处理List和外层easyRefresh的滑动冲突。 如何吧TabBarView塞进Sliver的全家桶里去呢,难道只能使用NestedScrollView+TabView这种组合么- - 。 请问大神有什么好的解决办法么,或者提供一下思路,万分感谢! image

您好,请问你怎么解决的。在不使用作者的新库的情况下,辛苦您了,我很头疼,项目太老了,不能使用最新的库

@xuelongqy
Copy link
Owner

@EsZhangHome Flutter版本是多少呢?

@EsZhangHome
Copy link

image
@EsZhangHome Flutter版本是多少呢?

flutter 2.0.4 dart 2.12.2

@xuelongqy
Copy link
Owner

image
@EsZhangHome Flutter版本是多少呢?

flutter 2.0.4 dart 2.12.2

因为用到了ProgressIndicatorTheme,AnimatedScale和CircularProgressIndicator.color。你可以对easy_refresh的源码进行简单的修改进行兼容。CircularProgressIndicator.color -> CircularProgressIndicator.valueColor。最好还是升级Flutter版本

@MrUncleX
Copy link

MrUncleX commented Mar 3, 2023

同样需求,有实现方案求告知

@lan2000
Copy link

lan2000 commented Apr 12, 2023

如果头部内容比较多的时候,下拉刷新,会导致TabBar触碰屏幕底部,这个时候报错了:

The following assertion was thrown during layout:
A RenderFlex overflowed by 2.2 pixels on the bottom.
The relevant error-causing widget was:
  Column


The overflowing RenderFlex has an orientation of Axis.vertical.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and
black striped pattern. This is usually caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the
RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be
seen. If the content is legitimately bigger than the available space, consider clipping it with a
ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex,
like a ListView.
The specific RenderFlex in question is: RenderFlex#5b952 OVERFLOWING:
  needs compositing
  creator: Column ← PrimaryScrollController ← _ExtendedSliverFillRemainingWithScrollable ←
    NestedScrollViewViewport ← IgnorePointer-[GlobalKey#6a289] ← Semantics ← Listener ←
    _GestureSemantics ← RawGestureDetector-[LabeledGlobalKey<RawGestureDetectorState>#f88cd] ←
    Listener ← _ScrollableScope ← _ScrollSemantics-[GlobalKey#23cd8] ← ⋯
  parentData: paintOffset=Offset(0.0, -0.0)
  constraints: BoxConstraints(w=360.0, h=45.8)
  size: Size(360.0, 45.8)
  direction: vertical
  mainAxisAlignment: start
  mainAxisSize: max
  crossAxisAlignment: center
  verticalDirection: down

完整代码:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';

class TabBarViewPage extends StatefulWidget {
  const TabBarViewPage({Key? key}) : super(key: key);

  @override
  TabBarViewPageState createState() {
    return TabBarViewPageState();
  }
}

class TabBarViewPageState extends State<TabBarViewPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  int _listCount = 20;
  int _gridCount = 20;

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

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

  @override
  Widget build(BuildContext context) {
    final themeData = Theme.of(context);
    return Scaffold(
      body: EasyRefresh.builder(
        header: ClassicHeader(
          clamping: true,
          position: IndicatorPosition.locator,
          mainAxisAlignment: MainAxisAlignment.end,
          dragText: 'Pull to refresh',
          armedText: 'Release ready',
          readyText: 'Refreshing...',
          processingText: 'Refreshing...',
          processedText: 'Succeeded',
          noMoreText: 'No more',
          failedText: 'Failed',
          messageText: 'Last updated at %T',
        ),
        footer: ClassicFooter(
          position: IndicatorPosition.locator,
          dragText: 'Pull to load',
          armedText: 'Release ready',
          readyText: 'Loading...',
          processingText: 'Loading...',
          processedText: 'Succeeded',
          noMoreText: 'No more',
          failedText: 'Failed',
          messageText: 'Last updated at %T',
        ),
        onRefresh: () async {
          await Future.delayed(const Duration(seconds: 2), () {
            if (mounted) {
              setState(() {
                if (_tabController.index == 0) {
                  _listCount = 20;
                } else {
                  _gridCount = 20;
                }
              });
            }
          });
        },
        onLoad: () async {
          await Future.delayed(const Duration(seconds: 2), () {
            if (mounted) {
              setState(() {
                if (_tabController.index == 0) {
                  _listCount += 10;
                } else {
                  _gridCount += 10;
                }
              });
            }
          });
        },
        childBuilder: (context, physics) {
          return ScrollConfiguration(
            behavior: const ERScrollBehavior(),
            child: ExtendedNestedScrollView(
              physics: physics,
              onlyOneScrollInBody: true,
              pinnedHeaderSliverHeightBuilder: () {
                return MediaQuery.of(context).padding.top + kToolbarHeight;
              },
              headerSliverBuilder: (context, innerBoxIsScrolled) {
                return <Widget>[
                  const HeaderLocator.sliver(clearExtent: false),
                  SliverAppBar(
                    expandedHeight: 120,
                    pinned: true,
                    flexibleSpace: FlexibleSpaceBar(
                      title: Text(
                        'TabBarView',
                        style: TextStyle(
                            color:
                            Theme.of(context).textTheme.titleLarge?.color),
                      ),
                      centerTitle: false,
                    ),
                  ),
                  SliverToBoxAdapter(
                    child: Container(
                      height: 500,
                      color: Colors.green,
                      child: const Text('其他内容'),
                    ),
                  )
                ];
              },
              body: Column(
                children: [
                  TabBar(
                    controller: _tabController,
                    labelColor: themeData.colorScheme.primary,
                    indicatorColor: themeData.colorScheme.primary,
                    tabs: const <Widget>[
                      Tab(
                        text: 'List',
                      ),
                      Tab(
                        text: 'Grid',
                      ),
                    ],
                  ),
                  Expanded(
                    child: TabBarView(
                      controller: _tabController,
                      children: <Widget>[
                        ExtendedVisibilityDetector(
                          uniqueKey: const Key('Tab0'),
                          child: _AutomaticKeepAlive(
                            child: CustomScrollView(
                              physics: physics,
                              slivers: [
                                SliverList(
                                    delegate: SliverChildBuilderDelegate(
                                            (context, index) {
                                          return SizedBox(
                                            height: 60,
                                            child: Text(index.toString()),
                                          );
                                        }, childCount: _listCount)),
                                const FooterLocator.sliver(),
                              ],
                            ),
                          ),
                        ),
                        ExtendedVisibilityDetector(
                          uniqueKey: const Key('Tab1'),
                          child: _AutomaticKeepAlive(
                            child: CustomScrollView(
                              physics: physics,
                              slivers: [
                                SliverGrid(
                                    delegate: SliverChildBuilderDelegate(
                                            (context, index) {
                                          return SizedBox(
                                            height: 60,
                                            child: Text(index.toString()),
                                          );
                                        }, childCount: _gridCount),
                                    gridDelegate:
                                    const SliverGridDelegateWithFixedCrossAxisCount(
                                      crossAxisCount: 2,
                                      childAspectRatio: 6 / 7,
                                    )),
                                const FooterLocator.sliver(),
                              ],
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

class _AutomaticKeepAlive extends StatefulWidget {
  final Widget child;

  const _AutomaticKeepAlive({
    Key? key,
    required this.child,
  }) : super(key: key);

  @override
  State<_AutomaticKeepAlive> createState() => _AutomaticKeepAliveState();
}

class _AutomaticKeepAliveState extends State<_AutomaticKeepAlive>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }

  @override
  bool get wantKeepAlive => true;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests