화면이 처음 구축 될 때 SingleChildScrollView에서 스크롤이 필요하지 않은지 어떻게 알 수 있습니까?

Nov 25 2020

나는 SingleChildScrollView. 때로는 child화면보다 길어서 SingleChildScrollView스크롤 할 수 있습니다. 그러나 때로는 child화면보다 짧아서 스크롤 할 필요가 없습니다.

나는 화면 하단에 화살표를 추가하여 사용자에게 나머지 내용을 볼 수 있거나 아래로 스크롤해야 함을 암시합니다. 나는 성공적으로 경우를 제외하고이 구현 child의이 SingleChildScrollView화면보다 짧습니다. 이 경우 스크롤이 필요하지 않으므로 화살표를 전혀 표시하지 않고 싶습니다.

listener이 작업을 수행 하려고 시도했지만 listener스크롤을 시작할 때까지 활성화되지 않으며이 경우 스크롤 할 수 없습니다.

또한 _scrollController화살표를 표시하는 삼항 연산자 의 속성에 액세스하려고 시도 했지만 예외가 발생합니다.ScrollController not attached to any scroll views.

다음은 내가하는 일을 보여주는 완전한 샘플 앱입니다. 실행되는 것을보고 싶다면 복사하여 붙여 넣기 만하면됩니다. 단순성을 위해 모든 내용 ColumnText위젯으로 대체했습니다 .

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

답변

VinayHP Nov 25 2020 at 19:15

먼저 hasClients 속성을 사용하여 _scrollController가 스크롤 뷰에 연결되어 있는지 확인해야합니다.

@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 initState() {
  super.initState();

  // Activated when you get to the bottom:
  _scrollController.addListener(() {
    if (_scrollController.hasClients){ // Like this
      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.hasClients){ // Like this
    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.hasClients){ // Like this
    if (_scrollController.offset == 0 &&
        _scrollController.position.extentAfter == 0) {
      setState(() {
        atBottom = false;
      });
    }}
  });
}
MichaelRodeman Nov 25 2020 at 22:49
  1. 스케줄러 Flutter 라이브러리를 가져옵니다.
import 'package:flutter/scheduler.dart';
  1. 상태 객체 내부에 부울 플래그를 생성하지만 build메서드 외부에 build아직 호출 되었는지 여부를 추적합니다 .
bool buildCalledYet = false;
  1. build메서드 시작 부분에 다음을 추가합니다 .
if (!firstBuild) {
      firstBuild = true;
      SchedulerBinding.instance.addPostFrameCallback((_) {
        setState(() {
          atBottom = !(_scrollController.position.maxScrollExtent > 0);
        });
      });
    }

(부울 플래그는이 코드 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,
                  ),
                ),
              ),
      ],
    );
  }
}

다른 스택 오버플로 질문에서이 솔루션을 찾았습니다. 스크롤 위젯 높이 결정