화면이 처음 구축 될 때 SingleChildScrollView에서 스크롤이 필요하지 않은지 어떻게 알 수 있습니까?
나는 SingleChildScrollView
. 때로는 child
화면보다 길어서 SingleChildScrollView
스크롤 할 수 있습니다. 그러나 때로는 child
화면보다 짧아서 스크롤 할 필요가 없습니다.
나는 화면 하단에 화살표를 추가하여 사용자에게 나머지 내용을 볼 수 있거나 아래로 스크롤해야 함을 암시합니다. 나는 성공적으로 경우를 제외하고이 구현 child
의이 SingleChildScrollView
화면보다 짧습니다. 이 경우 스크롤이 필요하지 않으므로 화살표를 전혀 표시하지 않고 싶습니다.
listener
이 작업을 수행 하려고 시도했지만 listener
스크롤을 시작할 때까지 활성화되지 않으며이 경우 스크롤 할 수 없습니다.
또한 _scrollController
화살표를 표시하는 삼항 연산자 의 속성에 액세스하려고 시도 했지만 예외가 발생합니다.ScrollController not attached to any scroll views.
다음은 내가하는 일을 보여주는 완전한 샘플 앱입니다. 실행되는 것을보고 싶다면 복사하여 붙여 넣기 만하면됩니다. 단순성을 위해 모든 내용 Column
을 Text
위젯으로 대체했습니다 .
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,
),
),
),
],
);
}
}
답변
먼저 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;
});
}}
});
}
- 스케줄러 Flutter 라이브러리를 가져옵니다.
import 'package:flutter/scheduler.dart';
- 상태 객체 내부에 부울 플래그를 생성하지만
build
메서드 외부에build
아직 호출 되었는지 여부를 추적합니다 .
bool buildCalledYet = false;
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,
),
),
),
],
);
}
}
다른 스택 오버플로 질문에서이 솔루션을 찾았습니다. 스크롤 위젯 높이 결정