您如何确定首次构建屏幕时在SingleChildScrollView中是否不需要滚动? [英] How do you tell if scrolling is not needed in a SingleChildScrollView when the screen is first built?
问题描述
我有一个 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,
),
),
),
],
);
}
}
推荐答案
- 导入调度程序Flutter库:
import 'package:flutter/scheduler.dart';
- 在状态对象内部但在
build
方法外部创建一个布尔标志,以跟踪是否已调用build
:
- Create a boolean flag inside the state object but outside of the
build
method to track whetherbuild
has been called yet:
bool buildCalledYet = false;
- 在
build
方法的开头添加以下内容:
- 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屋!