Flutter:ScrollController未附加到任何滚动视图

fcg9iug3  于 2023-05-01  发布在  Flutter
关注(0)|答案(2)|浏览(174)

在我的程序中,我尝试使用一个AnimatedBuilder小部件沿着无限滚动分页:

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final ScrollController _scrollController = ScrollController();
  // The item list where the data is stored.
  List<String> items = []; 
  // Flags used to check the loading status.
  bool loading = false, allLoaded = false;

  // Function that loads data in the item list.
  mockFetch() async {
    // No more data to load.
    if (allLoaded) {
      return;
    }   

    // Set the loading flag to true to prevent this function to be called several times.
    setState(() {
      loading = true;
    }); 

    // Simulates the delay that might occurs during an API call.
    await Future.delayed(Duration(milliseconds: 500));

    // If the item list is higher or equal to 60, an empty array is returned to simulate
    // the end of the data stream. If not, 20 more items are generated. 
    List<String> newData = items.length >= 60 ? [] : List.generate(20, (index) => "List Item ${index + items.length}");

    // Add the new data to the item list.
    if (newData.isNotEmpty) {
      items.addAll(newData);
    }

    // Reset the flags.
    setState(() {
      loading = false;
      // Returns false if the array is empty.
      allLoaded = newData.isEmpty;
    });
  }

  @override
  void initState() {
    super.initState();
    // Call the mockFetch() function when the state of MyHomePage is initialized for the first time.
    mockFetch();
    // Set a listener.
    _scrollController.addListener(() {

      if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent && !loading) {
        mockFetch();
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.deepOrange,
        title: Text("Home Page"),
      ),
      body: LayoutBuilder(builder: (context, constraints) {
        if (items.isNotEmpty) {
          return Stack(
            // Dispay the data.
            children: [
              ListView.separated(
                // Assign the scroll controller to the ListView.
                controller: _scrollController,
                itemBuilder: (context, index) {
                  // Display the newly loaded items.
                  if (index < items.length) {
                    return ListTile(
                      title: Text(items[index]),
                    );
                  }
                  // An extra item has been added to the index of the list meaning that
                  // all the items have been loaded.
                  else {
                    // Inform the user that there is no more data to load.
                    return Container(
                      width: constraints.maxWidth,
                      height: 50,
                      child: Center(
                        child: Text("Nothing more to load"),
                      ),
                    );
                  }
              },
              // Add a separator between each item.
              separatorBuilder: (context, index) {
                return Divider(height: 1);
              },
              // Add an extra item to the length of the list when all of the items are loaded.
              itemCount: items.length + (allLoaded ? 1 : 0)),

              if (loading)...[
                // Display a progress indicator at the bottom of the list whenever some data is loading.
                Positioned(
                  left: 0,
                  bottom: 0,
                  child: Container(
                    width: constraints.maxWidth,
                    height: 80,
                    child: Center(
                      child: CircularProgressIndicator(),
                    ),
                  )
                )
              ]
            ],
          );
        }
        else {
          return Container(
            child: Center(
              child: CircularProgressIndicator(),
            ),
          );
        }

      }
    ),

      bottomNavigationBar: AnimatedBuilder(
        animation: _scrollController,
        builder: (context, child) {
          return AnimatedContainer(
            duration: Duration(milliseconds: 300),
            height: _scrollController.position.userScrollDirection == ScrollDirection.reverse ? 0: 100,
            child: child,
          );  
        },  
        child: BottomNavigationBar(
          backgroundColor: Colors.amber[200],
          items: [
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: 'Home',
            ),  
            BottomNavigationBarItem(
              icon: Icon(Icons.child_friendly),
              label: 'Child',
            ),  
          ],  
        ),  
      ),  
    ); // Scaffold
  }

但我得到以下错误:
在构建AnimatedBuilder(animation:ScrollController#713ce(无客户端),脏,状态:_AnimatedState#02479):ScrollController未附加到任何滚动视图。'package:flutter/src/widgets/scroll_controller.dart ':Assert失败:第107行位置12:'_位置。不为空'
我读到过if(_scrollController.hasClients)通常可以解决这个问题,但我不知道如何在我的程序中使用它。
有人能帮帮我吗

wqnecbli

wqnecbli1#

问题是您试图访问_scrollController.position,而_scrollController没有附加到任何滚动视图。所以在访问_scrollController.position之前,检查它是否是hasClients,这应该意味着它被附加到滚动视图。就像这样:

AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  height: _scrollController.hasClients &&
          _scrollController.position.userScrollDirection ==
              ScrollDirection.reverse
      ? 0
      : 100,
  child: child,
)

现在错误应该消失了。

ha5z0ras

ha5z0ras2#

问题是initState在呈现第一个帧(build())之前被调用。尝试将所有scrollcontroller的东西 Package 在postFrameCallback中:

@override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      // attach listeners to scroll controllers
    });
    super.initState();
  }

在呈现第一个帧并将scrollcontroller附加到scrollview之后,将调用一次该函数

相关问题