xuelongqy / flutter_easy_refresh

A flutter widget that provides pull-down refresh and pull-up load.
https://xuelongqy.github.io/flutter_easy_refresh/
MIT License
3.88k stars 633 forks source link

NestedScrollView + TabBarView + EasyRefresh ,ClassicFooter 显示异常 #678

Open lan2000 opened 1 year ago

lan2000 commented 1 year ago

easy_refresh: ^3.3.0+1

下面代码,上拉加载更多结束之后ClassicFooter没有隐藏,任然显示在列表底部。 如下图: image image

环境: Flutter (Channel stable, 3.3.9, on Microsoft Windows [版本 10.0.19042.630], locale zh-CN) • Flutter version 3.3.9 on channel stable at D:\flutter\flutter • Upstream repository https://github.com/flutter/flutter/ • FLUTTER_GIT_URL = https://github.com/flutter/flutter/ • Framework revision b8f7f1f986 (3 months ago), 2022-11-23 06:43:51 +0900 • Engine revision 8f2221fbef • Dart version 2.18.5 • DevTools version 2.15.0 Connected device (4 available) • WLZ AL10 (mobile) • 10.168.3.210:5555 • android-arm64 • Android 10 (API 29)

代码:


import 'dart:async';

import 'package:example/widget/skeleton_item.dart';
import 'package:flutter/material.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:get/get.dart';

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

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

class NestedScrollViewPageState extends State<NestedScrollViewPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  EasyRefreshController controller1 = EasyRefreshController(
    controlFinishRefresh: true,
    controlFinishLoad: true,
  );

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

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

  bool noMoreRefresh = false;
  bool noMoreLoad = false;

  @override
  Widget build(BuildContext context) {
    // final themeData = Theme.of(context);
    return Scaffold(
        // appBar: AppBar(
        //   title: Text('AppBar'),
        // ),
        body: NestedScrollView(
      // controller: sc,
      headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
        var widgets = <Widget>[
          SliverAppBar(
            title: const Text('SliverAppBar'),
            expandedHeight: 200.0,
            pinned: true,
            floating: false,
            flexibleSpace: FlexibleSpaceBar(
                //centerTitle: true,
                collapseMode: CollapseMode.pin,
                background: Container(
                  color: Colors.transparent,
                )),
            bottom: TabBar(
              controller: _tabController,
              labelColor: Colors.black87,
              isScrollable: false,
              unselectedLabelColor: Colors.grey,
              tabs: const [
                Tab(text: "PAGE1"),
                Tab(text: "PAGE2"),
              ],
            ),
          ),
        ];
        return widgets;
      },
      body: TabBarView(
        controller: _tabController,
        children: const <Widget>[
          TestAPage(),
          TestAPage(),
        ],
      ),
    ));
  }
}

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

  @override
  State<TestAPage> createState() => _TestAPageState();
}

class _TestAPageState extends State<TestAPage>
    with AutomaticKeepAliveClientMixin {
  EasyRefreshController controller1 = EasyRefreshController(
    controlFinishRefresh: true,
    controlFinishLoad: true,
  );
  int _listCount = 10;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: EasyRefresh(
        controller: controller1,
        header: const MaterialHeader(
            // position: IndicatorPosition.locator,
            ),
        footer: const ClassicFooter(
          // clamping: true,
          // infiniteOffset: 2,
          // 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',
        ),
        child: CustomScrollView(
          slivers: [
            // const HeaderLocator.sliver(),
            SliverToBoxAdapter(
              child: TextButton(
                onPressed: () async {
                  print('----callRefresh');
                  controller1.callRefresh(force: true);
                },
                child: Text('callRefresh'),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate((context, index) {
                return SkeletonItem(
                  index: index,
                );
              }, childCount: _listCount),
            ),
            // const FooterLocator.sliver(),
          ],
        ),
        onRefresh: () async {
          await Future.delayed(const Duration(seconds: 2), () async {
            if (mounted) {
              setState(() {
                _listCount = 1;
              });
              controller1.finishRefresh(IndicatorResult.success);
              controller1.resetFooter();
            }
          });
        },
        // onLoad: () async {
        //   await Future.delayed(const Duration(seconds: 1), () {
        //     if (mounted) {
        //       setState(() {
        //         // _listCount = 0;
        //         _listCount += 1;
        //       });
        //       controller1.finishLoad(IndicatorResult.success);
        //     }
        //   });
        // },
        onLoad: () async {
          if (_listCount < 50) {
            await Future.delayed(const Duration(seconds: 2), () {
              if (mounted) {
                setState(() {
                  // _listCount = 0;
                  _listCount += 21;
                });
                controller1.finishLoad(IndicatorResult.success);
              }
            });
          } else {
            await Future.delayed(const Duration(seconds: 2), () {
              if (mounted) {
                setState(() {
                  // _listCount = 0;
                  _listCount += 11;
                });
                controller1.finishLoad(IndicatorResult.noMore);
              }
            });
          }
        },
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}
xuelongqy commented 1 year ago

可以使用FooterLocator,参考示例

lan2000 commented 1 year ago

可以使用FooterLocator,参考示例

使用FooterLocator的时候,下面的代码,上拉加载更多,一直到显示“no more”的时候,点击【设置内容为空】按钮,会报错。我是想在这种情况下设置内容为空,然后显示一个Empty View,但是报错了,是哪里弄错了吗。

错误日志:

======== Exception caught by rendering library =====================================================
The following assertion was thrown during performLayout():
RenderBox.size accessed beyond the scope of resize, layout, or permitted parent access. RenderBox can always access its own size, otherwise, the only object that is allowed to read RenderBox.size is its parent, if they have said they will. It you hit this assert trying to access a child's size, pass "parentUsesSize: true" to that child's layout().
'package:flutter/src/rendering/box.dart':
Failed assertion: line 2009 pos 13: 'debugDoingThisResize || debugDoingThisLayout || _computingThisDryLayout ||
              (RenderObject.debugActiveLayout == parent && size._canBeUsedByParent)'

代码:


import 'dart:async';

import 'package:example/widget/skeleton_item.dart';
import 'package:flutter/material.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:get/get.dart';

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

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

class NestedScrollViewPageState extends State<NestedScrollViewPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  EasyRefreshController controller1 = EasyRefreshController(
    controlFinishRefresh: true,
    controlFinishLoad: true,
  );

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

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

  bool noMoreRefresh = false;
  bool noMoreLoad = false;
  ScrollController sc = ScrollController();

  @override
  Widget build(BuildContext context) {
    // final themeData = Theme.of(context);
    return Scaffold(
      // appBar: AppBar(
      //   title: Text('AppBar'),
      // ),

      // body:      TestAPage(),
      body: NestedScrollView(
        // controller: sc,
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          var widgets = <Widget>[
            SliverAppBar(
              title: const Text('SliverAppBar'),
              expandedHeight: 200.0,
              pinned: true,
              floating: false,
              flexibleSpace: FlexibleSpaceBar(
                  //centerTitle: true,
                  collapseMode: CollapseMode.pin,
                  background: Container(
                    color: Colors.green,
                  )),
              bottom: TabBar(
                controller: _tabController,
                labelColor: Colors.black87,
                isScrollable: false,
                unselectedLabelColor: Colors.grey,
                tabs: const [
                  Tab(text: "PAGE1"),
                  Tab(text: "PAGE2"),
                ],
              ),
            ),
          ];
          return widgets;
        },
        body: TabBarView(
          controller: _tabController,
          children: <Widget>[
            TestAPage(sc: sc),
            TestAPage(sc: sc),
          ],
        ),
      ),
    );
  }
}

class TestAPage extends StatefulWidget {
  final ScrollController? sc;

  const TestAPage({Key? key, this.sc}) : super(key: key);

  @override
  State<TestAPage> createState() => _TestAPageState();
}

class _TestAPageState extends State<TestAPage>
    with AutomaticKeepAliveClientMixin {
  EasyRefreshController controller1 = EasyRefreshController(
    controlFinishRefresh: true,
    controlFinishLoad: true,
  );
  int _listCount = 10;
  ScrollController sc = ScrollController();
  bool enableLoad = true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: EasyRefresh(
        // scrollController: widget.sc,
        controller: controller1,
        fit: StackFit.expand,
        header: const MaterialHeader(
            // position: IndicatorPosition.locator,
            ),
        footer: const ClassicFooter(
          position: IndicatorPosition.locator,
          springRebound: false,
          dragText: 'Pull to load',
          armedText: 'Release ready',
          readyText: 'Loading...',
          processingText: 'Loading...',
          processedText: 'Succeeded',
          noMoreText: 'No more',
          failedText: 'Failed',
          messageText: 'Last updated at %T',
        ),
        onRefresh: onRefresh,
        onLoad: enableLoad
            ? () async {
                await Future.delayed(const Duration(seconds: 1), () {
                  if (mounted) {
                    setState(() {
                      _listCount += 11;
                    });
                    if (_listCount < 20) {
                      controller1.finishLoad(IndicatorResult.success);
                    } else {
                      controller1.finishLoad(IndicatorResult.noMore);
                    }
                  }
                });
              }
            : null,
        child: LayoutBuilder(
          builder: (context, constraint) {
            return CustomScrollView(
              // controller: widget.sc,
              slivers: [
                // if (_listCount != 0)
                SliverList(
                  delegate: SliverChildBuilderDelegate((context, index) {
                    return SkeletonItem(
                      index: index,
                    );
                  }, childCount: _listCount),
                ),
                // if (_listCount == 0)
                //   SliverToBoxAdapter(
                //     child: Container(
                //       color: Colors.redAccent,
                //       height: constraint.maxHeight,
                //     ),
                //   ),
                if (_listCount == 0)
                  SliverFillRemaining(
                    hasScrollBody: false,
                    child: Container(
                      color: Colors.redAccent,
                      height: constraint.maxHeight,
                      child: const Text('Empty View'),
                    ),
                  ),
                if (_listCount != 0)
                  SliverToBoxAdapter(
                    child: TextButton(
                      onPressed: () async {
                        _listCount = 0;
                        enableLoad = false;
                        setState(() {});
                      },
                      child: const Text('设置内容为空'),
                    ),
                  ),
                const FooterLocator.sliver(),
              ],
            );
          },
        ),
      ),
    );
  }

  FutureOr<dynamic> onRefresh() async {
    await Future.delayed(const Duration(seconds: 2), () async {
      if (mounted) {
        setState(() {
          _listCount = 0;
        });
        controller1.finishRefresh(IndicatorResult.success);
        controller1.resetFooter();
      }
    });
  }

  @override
  bool get wantKeepAlive => true;
}
xuelongqy commented 1 year ago

3.3.1,我试了没报错呢?

lan2000 commented 1 year ago

3.3.1,我试了没报错呢?

我当时测试的时候,是3.3.0版本,3.3.1还没出来。3.3.1版本已经修复这个问题?

cc1990 commented 1 year ago

3.3.1,我试了没报错呢?

我当时测试的时候,是3.3.0版本,3.3.1还没出来。3.3.1版本已经修复这个问题?

我现在用的3.3.1的也还是有这个问题

xuelongqy commented 1 year ago

@lan2000 @cc1990 我这边没报错Flutter3.7.5,你们的Flutter版本是?

lan2000 commented 1 year ago

@lan2000 @cc1990 我这边没报错Flutter3.7.5,你们的Flutter版本是?

前文有的

环境:
Flutter (Channel stable, 3.3.9, on Microsoft Windows [版本 10.0.19042.630], locale zh-CN)
• Flutter version 3.3.9 on channel stable at D:\flutter\flutter
• Upstream repository https://github.com/flutter/flutter/
• FLUTTER_GIT_URL = https://github.com/flutter/flutter/
• Framework revision b8f7f1f986 (3 months ago), 2022-11-23 06:43:51 +0900
• Engine revision 8f2221fbef
• Dart version 2.18.5
• DevTools version 2.15.0
Connected device (4 available)
• WLZ AL10 (mobile) • 10.168.3.210:5555 • android-arm64 • Android 10 (API 29)
qiaodongliang commented 1 year ago

Flutter 3.10.0,easy_refresh: ^3.3.2+1 遇到同样的问题

YaakovHuang commented 10 months ago

Flutter 3.13.8 Dart 3.1.4 easy_refresh 3.3.2+1 也存在相同的问题