您如何确定首次构建屏幕时在SingleChildScrollView中是否不需要滚动? [英] How do you tell if scrolling is not needed in a SingleChildScrollView when the screen is first built?

查看:54
本文介绍了您如何确定首次构建屏幕时在SingleChildScrollView中是否不需要滚动?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a SingleChildScrollView. Sometimes its child is longer than the screen, in which case the SingleChildScrollView lets you scroll. But also sometimes its child is shorter than the screen, in which case no scrolling is needed.

我正在尝试在屏幕底部添加一个箭头,向用户暗示他们可以/应该向下滚动以查看其余内容.我成功地实现了这一点,除非 SingleChildScrollView child 比屏幕短.在这种情况下,不需要滚动,因此我完全不想显示箭头.

I am trying to add an arrow to the bottom of the screen which hints to the user that they can/should scroll down to see the rest of the contents. I successfully implemented this except in the case where the child of the SingleChildScrollView is shorter than the screen. In this case no scrolling is needed, so I would like to not show the arrow at all.

我尝试制作一个 listener 来执行此操作,但是 listener 直到您开始滚动时才被激活,在这种情况下您无法滚动.

I've tried making a listener to do this, but the listener isn't activated until you start scrolling, and in this case you can't scroll.

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

I've also tried accessing the properties of the _scrollController in the ternary operator which shows the arrow, but an exception is thrown: ScrollController not attached to any scroll views.

这是一个完整的示例应用程序,显示了我在做什么,因此,如果您想查看它的运行情况,则只需复制并粘贴它即可.为了简单起见,我用 Text 小部件的 Column 替换了所有内容:

Here is a complete sample app showing what I'm doing, so you can just copy and paste it if you want to see it run. I replaced all of the contents with a Column of Text widgets for simplicity:

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,
                  ),
                ),
              ),
      ],
    );
  }
}

推荐答案

  1. 导入调度程序Flutter库:

import 'package:flutter/scheduler.dart';

  1. 在状态对象内部但在 build 方法外部创建一个布尔标志,以跟踪是否已调用 build :
  1. Create a boolean flag inside the state object but outside of the build method to track whether build has been called yet:

bool buildCalledYet = false;

  1. build 方法的开头添加以下内容:
  1. Add the following in the beginning of the build method:

if (!firstBuild) {
      firstBuild = true;
      SchedulerBinding.instance.addPostFrameCallback((_) {
        setState(() {
          atBottom = !(_scrollController.position.maxScrollExtent > 0);
        });
      });
    }

(布尔标志防止此代码导致一遍又一遍地调用 build .)

(The boolean flag prevents this code from causing build to be called over and over again.)

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

Here's the complete code of the sample app implementing this solution:

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,
                  ),
                ),
              ),
      ],
    );
  }
}

我在另一个堆栈溢出问题上找到了该解决方案:确定滚动小部件的高度

I found this solution on another Stack Overflow question: Determine Scroll Widget height

这篇关于您如何确定首次构建屏幕时在SingleChildScrollView中是否不需要滚动?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆