flutter 检索一个小部件的scrollController到另一个小部件?

yftpprvb  于 2023-05-08  发布在  Flutter
关注(0)|答案(1)|浏览(155)

在我的应用程序中,一个带有底部导航栏的Menu类通过IndexedStack小部件显示应用程序的不同页面,以保存每个页面的状态:
menu.dart

class Menu extends StatefulWidget {
  @override
  _MenuState createState() => _MenuState();
}

class _MenuState extends State<Menu> {
  final List<Widget> pages = [Home(), Profile()];
  int _selectedIndex = 0;

  void callbackFunction(int index) {
    setState(() {
        _selectedIndex = index;
    }); 
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Save State'),
      ),  

      body: IndexedStack(
        index: _selectedIndex,
        children: pages,
      ),  

      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),  
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),  
        ],  
        currentIndex: _selectedIndex,
        onTap: (index){
            setState(() {
                _selectedIndex = index;
            });
        },
      ),
    );
  }
}

home.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(
      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(),
            ),
          );
        }

      }
    ),

    ); // Scaffold
  }
}

现在的事情是我想我的底部导航栏显示或隐藏根据滚动方向进入主页。
所以我修改了menu中的导航栏。dart:

bottomNavigationBar: AnimatedBuilder(
animation: _scrollController,
builder: (context, child) {
  return AnimatedContainer(
    duration: Duration(milliseconds: 300),
    height: _scrollController.hasClients && _scrollController.position.userScrollDirection == ScrollDirection.reverse ? 0: kBottomNavigationBarHeight,
    child: child,
  );
},

child: SingleChildScrollView(
    physics: const NeverScrollableScrollPhysics(),
    child: BottomNavigationBar(
items: [
  BottomNavigationBarItem(
    icon: Icon(Icons.home),
    label: 'Home',
  ),  
  BottomNavigationBarItem(
    icon: Icon(Icons.person),
    label: 'Profile',
  ),  
],  
currentIndex: _selectedIndex,
    onTap: (index){
        setState(() {
            _selectedIndex = index;
        });
    },
  ),
),

),
当然,我需要从主页检索_scrollController变量以使AnimatedBuilder工作。
我尝试了不同的策略(回调函数,getter...),但到目前为止没有一个有效。
有没有一种方法可以从菜单小部件中获取_scrollController变量,或者我应该考虑另一种方法?

kuuvgm7e

kuuvgm7e1#

您需要从Home连接回_MenuState。
您的_MenuState需要知道的内容可以包含在变量中:

bool isNavigationBarVisible = true;

在你的AnimatedContainer中,使高度依赖于:

height: isNavigationBarVisible ? kBottomNavigationBarHeight : 0,

向_MenuState添加回调函数:

void callbackNavigationBarVisible(bool visible) {
    setState(() {
        isNavigationBarVisible = visible;
    });

改变你的家:

class Home extends StatefulWidget {

Home({Key? key, reuired, required this.callback}) : super(key: key);
      final void Function(bool visible) callback;
    
      @override
      State<Home> createState() => _HomeState();
    }

当你示例化Home的时候,移交回调函数:

final List<Widget> pages = [Home(callback: callbackNavigationBarVisible), Profile()];

在您的_HomeState中更改以下内容:

_scrollController.addListener(() {
          if(_scrollController.hasClients && _scrollController.position.userScrollDirection == ScrollDirection.reverse){
            widget.callback(false);
          }else{
            widget.callback(true);
          }
          if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent && !loading) {
                mockFetch();
          }
    });

相关问题