滚动带有ScrollController的CustomScrollView时,SliverAppbar仍然可见

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滚动带有ScrollController的CustomScrollView时,SliverAppbar仍然可见相关的知识,希望对你有一定的参考价值。

将ScrollController添加到函数timelineList()会导致SliverAppBar在滚动时保持可见(当滚动计数器列表时,SliverAppBar应该隐藏)。如果从列表中删除_scrollController(请参阅timelineList函数),问题就会消失,但这会引发一个新问题,我需要在滚动条到达底部时收听(获取更多内容)。

请参阅下面的示例应用,复制/粘贴并运行。

void main() => runApp(TestApp());
class TestApp extends StatelessWidget {
  final _scrollController = new ScrollController();
  TestApp(){
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        print('Get more data');
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    TestBloc bloc = TestBloc();
    bloc.fetchTestTimeline();
    bloc.fetchTestAppBarTxt1();
    bloc.fetchTestAppBarTxt2();
    return MaterialApp(
      home: new Scaffold(
          backgroundColor: Colors.grey[200],
          appBar: AppBar(
            backgroundColor: Colors.blueGrey,
            elevation: 0.0,
          ),
          body: NestedScrollView(
            headerSliverBuilder:
                (BuildContext contrxt, bool innerBoxIsScrolled) {
              return <Widget>[
                buildSliverAppBar(context, bloc),
              ];
            },
            body: Column(
              children: <Widget>[
                timelineList(bloc),
              ],
            )

          )),
    );
  }

  buildSliverAppBar(context, TestBloc bloc){
    return SliverAppBar(
        automaticallyImplyLeading: false,
        backgroundColor: Colors.grey[400],
        expandedHeight: 200.0,
        floating: true,
        snap: true,
        flexibleSpace: FlexibleSpaceBar(
          background: Column(
            children: <Widget>[
              Container(
                padding: EdgeInsets.only(left: 2.0),
                height: 200,
                child: Column(
                  children: <Widget>[
                    StreamBuilder(
                        stream: bloc.testAppBarTxt1,
                        initialData: null,
                        builder: (BuildContext context,
                            AsyncSnapshot<String> snapshot) {
                          if (snapshot.data == null)
                            return buildProgressIndicator(true);
                          return Expanded(
                              child: Text('${snapshot.data}'));
                        }),
                    StreamBuilder(
                        stream: bloc.testAppBarTxt2,
                        initialData: null,
                        builder: (BuildContext context,
                            AsyncSnapshot<String> snapshot) {
                          if (snapshot.data == null)
                            return buildProgressIndicator(true);
                          return Expanded(
                              child: Text('${snapshot.data}'));
                        }),
                  ],
                ),
              )
            ],
          ),
        ));
  }

  timelineList(TestBloc bloc) {
    return StreamBuilder(
        stream: bloc.getTestTimeline,
        initialData: null,
        builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Expanded(child: buildProgressIndicator(true));
          }

          List<int> val = snapshot.data;

          if (val.isNotEmpty) {
            addToTimelineList(val, bloc);
            return Expanded(
              child: CustomScrollView(
                controller: _scrollController,
                slivers: <Widget>[
                  SliverList(
                      delegate: SliverChildListDelegate(new List<Widget>.generate(bloc.listTest.length, (int index) {
                        if (index == bloc.listTest.length) {
                          return buildProgressIndicator(bloc.isPerformingRequest);
                        } else {
                          return bloc.listTest[index];
                        }
                      })
                      ))
                ],

              ),
            );
          }
        });
  }
  void addToTimelineList(List<int> list, TestBloc bloc) {
    for (var val in list) {
      bloc.listTest.add(Text('$val'));
    }
  }
}

Widget buildProgressIndicator(showIndicator) {
  return new Padding(
    padding: const EdgeInsets.all(8.0),
    child: new Center(
      child: new Opacity(
        opacity: showIndicator ? 1.0 : 0.0,
        child: Container(
            width: 10.0,
            height: 10.0,
            child: new CircularProgressIndicator(
            )),
      ),
    ),
  );
}
class TestBloc {
  String appbar1Val;
  String appbar2Val;
  List<Text> listTest = new List<Text>();
  bool isPerformingRequest = false;
  final _testAppBarText1 = BehaviorSubject<String>();

  Observable<String> get testAppBarTxt1 => _testAppBarText1.stream;
  final _testAppBarText2 = BehaviorSubject<String>();

  Observable<String> get testAppBarTxt2 => _testAppBarText2.stream;
  final _testTimeline = PublishSubject<List<int>>();

  Observable<List<int>> get getTestTimeline => _testTimeline.stream;

  fetchTestTimeline() async {
    List item = await Future.delayed(
        Duration(seconds: 2), () => List<int>.generate(100, (i) => i));
    _testTimeline.sink.add(item);
  }

  fetchTestAppBarTxt1() async {
    appbar1Val = await Future.delayed(Duration(seconds: 2), () => "Text One");

    _testAppBarText1.sink.add(appbar1Val);
  }

  fetchTestAppBarTxt2() async {
    appbar2Val = await Future.delayed(Duration(seconds: 2), () => "Text Two");
    _testAppBarText2.sink.add(appbar2Val);
  }
  dispose() {
    _testAppBarText1.close();
    _testAppBarText2.close();
    _testTimeline.close();
  }
}
答案

使用Notification Listener包装列表可以获得相同的结果。

NotificationListener<ScrollNotification>(
                onNotification: (sn) {
                  if (sn.metrics.pixels ==
                      sn.metrics.maxScrollExtent) {
                    print('Get more data');
                  }
                },
                child: CustomScrollView(...

编辑:由于我的初始答案没有涵盖animateTo用例,我通过删除外部NestedScrollView使其工作。这是修改后的例子。

void main() => runApp(TestApp());

class TestApp extends StatelessWidget {
  final _scrollController = new ScrollController();
  TestApp() {
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        print('Get more data');
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    TestBloc bloc = TestBloc();
    bloc.fetchTestTimeline();
    bloc.fetchTestAppBarTxt1();
    bloc.fetchTestAppBarTxt2();
    return MaterialApp(
      home: new Scaffold(
        backgroundColor: Colors.grey[200],
        appBar: AppBar(
          backgroundColor: Colors.blueGrey,
          elevation: 0.0,
        ),
        body: Column(
          children: <Widget>[
            timelineList(bloc),
          ],
        ),
      ),
    );
  }

  buildSliverAppBar(context, TestBloc bloc) {
    return SliverAppBar(
        automaticallyImplyLeading: false,
        backgroundColor: Colors.grey[400],
        expandedHeight: 200.0,
        floating: true,
        snap: true,
        flexibleSpace: FlexibleSpaceBar(
          background: Column(
            children: <Widget>[
              Container(
                padding: EdgeInsets.only(left: 2.0),
                height: 200,
                child: Column(
                  children: <Widget>[
                    StreamBuilder(
                        stream: bloc.testAppBarTxt1,
                        initialData: null,
                        builder: (BuildContext context,
                            AsyncSnapshot<String> snapshot) {
                          if (snapshot.data == null)
                            return buildProgressIndicator(true);
                          return Expanded(child: Text('${snapshot.data}'));
                        }),
                    StreamBuilder(
                        stream: bloc.testAppBarTxt2,
                        initialData: null,
                        builder: (BuildContext context,
                            AsyncSnapshot<String> snapshot) {
                          if (snapshot.data == null)
                            return buildProgressIndicator(true);
                          return Expanded(child: Text('${snapshot.data}'));
                        }),
                  ],
                ),
              )
            ],
          ),
        ));
  }

  timelineList(TestBloc bloc) {
    return StreamBuilder(
        stream: bloc.getTestTimeline,
        initialData: null,
        builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Expanded(child: buildProgressIndicator(true));
          }

          List<int> val = snapshot.data;

          if (val.isNotEmpty) {
            addToTimelineList(val, bloc);
            return Expanded(
              child: CustomScrollView(
                controller: _scrollController,
                slivers: <Widget>[
                  buildSliverAppBar(context, bloc),
                  SliverList(
                      delegate: SliverChildListDelegate(
                          new List<Widget>.generate(bloc.listTest.length,
                              (int index) {
                    if (index == bloc.listTest.length) {
                      return buildProgressIndicator(bloc.isPerformingRequest);
                    } else {
                      return bloc.listTest[index];
                    }
                  })))
                ],
              ),
            );
          }
        });
  }

  void addToTimelineList(List<int> list, TestBloc bloc) {
    for (var val in list) {
      bloc.listTest.add(Text('$val'));
    }
  }
}

Widget buildProgressIndicator(showIndicator) {
  return new Padding(
    padding: const EdgeInsets.all(8.0),
    child: new Center(
      child: new Opacity(
        opacity: showIndicator ? 1.0 : 0.0,
        child: Container(
            width: 10.0, height: 10.0, child: new CircularProgressIndicator()),
      ),
    ),
  );
}

class TestBloc {
  String appbar1Val;
  String appbar2Val;
  List<Text> listTest = new List<Text>();
  bool isPerformingRequest = false;
  final _testAppBarText1 = BehaviorSubject<String>();

  Observable<String> get testAppBarTxt1 => _testAppBarText1.stream;
  final _testAppBarText2 = BehaviorSubject<String>();

  Observable<String> get testAppBarTxt2 => _testAppBarText2.stream;
  final _testTimeline = PublishSubject<List<int>>();

  Observable<List<int>> get getTestTimeline => _testTimeline.stream;

  fetchTestTimeline() async {
    List item = await Future.delayed(
        Duration(seconds: 2), () => List<int>.generate(100, (i) => i));
    _testTimeline.sink.add(item);
  }

  fetchTestAppBarTxt1() async {
    appbar1Val = await Future.delayed(Duration(seconds: 2), () => "Text One");

    _testAppBarText1.sink.add(appbar1Val);
  }

  fetchTestAppBarTxt2() async {
    appbar2Val = await Future.delayed(Duration(seconds: 2), () => "Text Two");
    _testAppBarText2.sink.add(appbar2Val);
  }

  dispose() {
    _testAppBarText1.close();
    _testAppBarText2.close();
    _testTimeline.close();
  }
}

以上是关于滚动带有ScrollController的CustomScrollView时,SliverAppbar仍然可见的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 可滚动组件:ScrollController

ScrollController 未附加到任何滚动视图

颤振错误:'ScrollController 未附加到任何滚动视图。'在滚动

如何使用 ScrollController 检查 ListView 滚动速度

25.Flutter的ListView监听滚动事件之ScrollController

Flutter 之 滚动监听及控制(十九)