Flutter 当屏幕首次构建时,如何判断SingleChildScrollView中是否不需要滚动?

Flutter 当屏幕首次构建时,如何判断SingleChildScrollView中是否不需要滚动?,flutter,dart,Flutter,Dart,我有一个SingleChildScrollView。有时它的child比屏幕长,在这种情况下,SingleChildScrollView允许您滚动。但有时它的子项比屏幕短,在这种情况下不需要滚动 我试图在屏幕底部添加一个箭头,提示用户可以/应该向下滚动查看其余内容。我成功地实现了这一点,除非SingleChildScrollView的子视图比屏幕短。在这种情况下,不需要滚动,所以我想根本不显示箭头 我已经尝试制作一个侦听器来执行此操作,但是侦听器在您开始滚动之前不会被激活,在这种情况下,您无法滚

我有一个
SingleChildScrollView
。有时它的
child
比屏幕长,在这种情况下,
SingleChildScrollView
允许您滚动。但有时它的
子项
比屏幕短,在这种情况下不需要滚动

我试图在屏幕底部添加一个箭头,提示用户可以/应该向下滚动查看其余内容。我成功地实现了这一点,除非
SingleChildScrollView
子视图比屏幕短。在这种情况下,不需要滚动,所以我想根本不显示箭头

我已经尝试制作一个
侦听器来执行此操作,但是
侦听器在您开始滚动之前不会被激活,在这种情况下,您无法滚动

我还尝试在显示箭头的三元运算符中访问
\u scrollController
的属性,但引发了一个异常:
scrollController未附加到任何滚动视图。

这里有一个完整的示例应用程序,展示了我正在做的事情,所以如果你想看到它运行,你可以复制并粘贴它。为了简单起见,我用
文本
小部件的
替换了所有内容:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) =>
      MaterialApp(home: Scaffold(body: MyScreen()));
}

class MyScreen extends StatefulWidget {
  @override
  _MyScreenState createState() => _MyScreenState();
}

class _MyScreenState extends State<MyScreen> {
  ScrollController _scrollController = ScrollController();
  bool atBottom = false;

  @override
  void initState() {
    super.initState();

    // Activated when you get to the bottom:
    _scrollController.addListener(() {
      if (_scrollController.position.extentAfter == 0) {
        setState(() {
          atBottom = true;
        });
      }
    });

    // Activated as soon as you start scrolling back up after getting to the bottom:
    _scrollController.addListener(() {
      if (_scrollController.position.extentAfter > 0 && atBottom) {
        setState(() {
          atBottom = false;
        });
      }
    });

    // I want this to activate if you are at the top of the screen and there is
    // no scrolling to do, i.e. the widget being displayed fits in the screen:
    _scrollController.addListener(() {
      if (_scrollController.offset == 0 &&
          _scrollController.position.extentAfter == 0) {
        setState(() {
          atBottom = false;
        });
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        SingleChildScrollView(
          controller: _scrollController,
          scrollDirection: Axis.vertical,
          child: Container(
            width: MediaQuery.of(context).size.width,
            child: Column(
              children: [
                for (int i = 0; i < 100; i++)
                  Text(
                    i.toString(),
                  ),
              ],
            ),
          ),
        ),
        atBottom
            ? Container()
            : Positioned(
                bottom: 10,
                right: 10,
                child: Container(
                  child: Icon(
                    Icons.arrow_circle_down,
                  ),
                ),
              ),
      ],
    );
  }
}
导入“包装:颤振/材料.省道”;
void main()=>runApp(MyApp());
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文)=>
MaterialApp(主页:脚手架(主体:MyScreen());
}
类MyScreen扩展StatefulWidget{
@凌驾
_MyScreenState createState()=>\u MyScreenState();
}
类_MyScreenState扩展状态{
ScrollController_ScrollController=ScrollController();
bool-atBottom=false;
@凌驾
void initState(){
super.initState();
//在到达底部时激活:
_scrollController.addListener((){
如果(_scrollController.position.extentAfter==0){
设置状态(){
atBottom=true;
});
}
});
//在到达底部后开始向上滚动时激活:
_scrollController.addListener((){
if(_scrollController.position.extentAfter>0&&atBottom){
设置状态(){
atBottom=false;
});
}
});
//我想这是激活,如果你在屏幕的顶部,有
//无需滚动,即显示的小部件适合屏幕:
_scrollController.addListener((){
如果(_scrollController.offset==0&&
_scrollController.position.extentAfter==0){
设置状态(){
atBottom=false;
});
}
});
}
@凌驾
无效处置(){
_scrollController.dispose();
super.dispose();
}
@凌驾
小部件构建(构建上下文){
返回堆栈(
儿童:[
SingleChildScrollView(
控制器:\ u滚动控制器,
滚动方向:轴垂直,
子:容器(
宽度:MediaQuery.of(context).size.width,
子:列(
儿童:[
对于(int i=0;i<100;i++)
正文(
i、 toString(),
),
],
),
),
),
底层
?容器()
:定位(
底部:10,
右:10,,
子:容器(
子:图标(
图标。箭头\圆圈\向下,
),
),
),
],
);
}
}
  • 导入计划程序颤振库:
  • 在state对象内部但在
    build
    方法外部创建一个布尔标志,以跟踪是否已调用
    build
  • build
    方法的开头添加以下内容:
  • (布尔标志防止此代码导致反复调用
    build
    。)

    以下是实现此解决方案的示例应用程序的完整代码:

    import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) =>
          MaterialApp(home: Scaffold(body: MyScreen()));
    }
    
    class MyScreen extends StatefulWidget {
      @override
      _MyScreenState createState() => _MyScreenState();
    }
    
    class _MyScreenState extends State<MyScreen> {
      ScrollController _scrollController = ScrollController();
      bool atBottom = false;
      // ======= new code =======
      bool buildCalledYet = false;
      // ========================
    
      @override
      void initState() {
        super.initState();
    
        _scrollController.addListener(() {
          if (_scrollController.position.extentAfter == 0) {
            setState(() {
              atBottom = true;
            });
          }
        });
    
        _scrollController.addListener(() {
          if (_scrollController.position.extentAfter > 0 && atBottom) {
            setState(() {
              atBottom = false;
            });
          }
        });
    
        // ======= The third listener is not needed. =======
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        // =========================== new code ===========================
        if (!buildCalledYet) {
          buildCalledYet = true;
          SchedulerBinding.instance.addPostFrameCallback((_) {
            setState(() {
              atBottom = !(_scrollController.position.maxScrollExtent > 0);
            });
          });
        }
        // ================================================================
    
        return Stack(
          children: [
            SingleChildScrollView(
              controller: _scrollController,
              scrollDirection: Axis.vertical,
              child: Container(
                width: MediaQuery.of(context).size.width,
                child: Column(
                  children: [
                    for (int i = 0; i < 100; i++)
                      Text(
                        i.toString(),
                      ),
                  ],
                ),
              ),
            ),
            atBottom
                ? Container()
                : Positioned(
                    bottom: 10,
                    right: 10,
                    child: Container(
                      child: Icon(
                        Icons.arrow_circle_down,
                      ),
                    ),
                  ),
          ],
        );
      }
    }
    
    导入“包装:颤振/材料.省道”;
    导入“package:flatter/scheduler.dart”;
    void main()=>runApp(MyApp());
    类MyApp扩展了无状态小部件{
    @凌驾
    小部件构建(构建上下文)=>
    MaterialApp(主页:脚手架(主体:MyScreen());
    }
    类MyScreen扩展StatefulWidget{
    @凌驾
    _MyScreenState createState()=>\u MyScreenState();
    }
    类_MyScreenState扩展状态{
    ScrollController_ScrollController=ScrollController();
    bool-atBottom=false;
    //=======新代码=======
    bool buildCalledYet=false;
    // ========================
    @凌驾
    void initState(){
    super.initState();
    _scrollController.addListener((){
    如果(_scrollController.position.extentAfter==0){
    设置状态(){
    atBottom=true;
    });
    }
    });
    _scrollController.addListener((){
    if(_scrollController.position.extentAfter>0&&atBottom){
    设置状态(){
    atBottom=false;
    });
    }
    });
    //=======不需要第三个侦听器=======
    }
    @凌驾
    无效处置(){
    _scrollController.dispose();
    super.dispose();
    }
    @凌驾
    小部件构建(构建上下文){
    //========================================新代码===========================
    如果(!buildCalledYet){
    buildCalledYet=true;
    SchedulerBinding.instance.addPostFrameCallback((){
    设置状态(){
    atBottom=!(_scrollController.position.maxScrollExtent>0);
    });
    });
    }
    // ================================================================
    返回堆栈(
    儿童:[
    SingleChildScrollView(
    控制器:\ u滚动控制器,
    滚动方向:轴垂直,
    子:容器(
    宽度:MediaQuery.of(context).size.width,
    儿童:公司
    
    bool buildCalledYet = false;
    
    if (!firstBuild) {
          firstBuild = true;
          SchedulerBinding.instance.addPostFrameCallback((_) {
            setState(() {
              atBottom = !(_scrollController.position.maxScrollExtent > 0);
            });
          });
        }
    
    import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) =>
          MaterialApp(home: Scaffold(body: MyScreen()));
    }
    
    class MyScreen extends StatefulWidget {
      @override
      _MyScreenState createState() => _MyScreenState();
    }
    
    class _MyScreenState extends State<MyScreen> {
      ScrollController _scrollController = ScrollController();
      bool atBottom = false;
      // ======= new code =======
      bool buildCalledYet = false;
      // ========================
    
      @override
      void initState() {
        super.initState();
    
        _scrollController.addListener(() {
          if (_scrollController.position.extentAfter == 0) {
            setState(() {
              atBottom = true;
            });
          }
        });
    
        _scrollController.addListener(() {
          if (_scrollController.position.extentAfter > 0 && atBottom) {
            setState(() {
              atBottom = false;
            });
          }
        });
    
        // ======= The third listener is not needed. =======
      }
    
      @override
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        // =========================== new code ===========================
        if (!buildCalledYet) {
          buildCalledYet = true;
          SchedulerBinding.instance.addPostFrameCallback((_) {
            setState(() {
              atBottom = !(_scrollController.position.maxScrollExtent > 0);
            });
          });
        }
        // ================================================================
    
        return Stack(
          children: [
            SingleChildScrollView(
              controller: _scrollController,
              scrollDirection: Axis.vertical,
              child: Container(
                width: MediaQuery.of(context).size.width,
                child: Column(
                  children: [
                    for (int i = 0; i < 100; i++)
                      Text(
                        i.toString(),
                      ),
                  ],
                ),
              ),
            ),
            atBottom
                ? Container()
                : Positioned(
                    bottom: 10,
                    right: 10,
                    child: Container(
                      child: Icon(
                        Icons.arrow_circle_down,
                      ),
                    ),
                  ),
          ],
        );
      }
    }