ListView
中滚动到特定的小部件?
例如,如果我按下特定按钮,我想自动滚动到ListView
中的某个Container
。ListView(children: <Widget>[
Container(...),
Container(...), #scroll for example to this container
Container(...)
]);
ListView
中滚动到特定的小部件?
例如,如果我按下特定按钮,我想自动滚动到ListView
中的某个Container
。ListView(children: <Widget>[
Container(...),
Container(...), #scroll for example to this container
Container(...)
]);
目前来看,最简单的解决方案是使用Scrollable.ensureVisible(context)
。它会为你做所有的事情,并适用于任何小部件大小。可以通过GlobalKey
获取上下文。
问题在于,ListView
不会呈现非可见项,意味着你的目标可能根本不会被构建出来。这意味着你的目标没有context
,如果不进行更多的工作,就无法使用该方法。
最后,最简单的解决方案是将ListView
替换为SingleChildScrollView
,并将其子元素包装在Column
中。例如:
class ScrollView extends StatelessWidget {
final dataKey = new GlobalKey();
@override
Widget build(BuildContext context) {
return new Scaffold(
primary: true,
appBar: new AppBar(
title: const Text('Home'),
),
body: new SingleChildScrollView(
child: new Column(
children: <Widget>[
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
new SizedBox(height: 160.0, width: double.infinity, child: new Card()),
// destination
new Card(
key: dataKey,
child: new Text("data\n\n\n\n\n\ndata"),
)
],
),
),
bottomNavigationBar: new RaisedButton(
onPressed: () => Scrollable.ensureVisible(dataKey.currentContext),
child: new Text("Scroll to data"),
),
);
}
}
注意:虽然这样可以轻松滚动到所需的项目,但仅适用于小型预定义列表。对于更大的列表,您将面临性能问题。
但是,可以通过更多工作来让Scrollable.ensureVisible
与ListView
一起使用。
ListView
和SingleChildScrollView
是完全不同的东西。如果使用情况适合于SingleChildScrollView
,那么这个问题本来就不存在。 - Sarp BaşaranerWidgetsBinding.instance.addPostFrameCallback((_) => Scrollable.ensureVisible(dataKey.currentContext))
。 - ychScrollable.ensureVisible
与ListView
的配合使用所需的“更多工作”! - Karolina Hagegård不幸的是,ListView没有内置的scrollToIndex()函数。您必须开发自己的方法来测量该元素的偏移量,以便使用animateTo()
或jumpTo()
进行滚动,或者您可以搜索这些建议的解决方案/插件或其他帖子,例如flutter ListView scroll to index not available。
(自2017年以来,一般的scrollToIndex问题已在flutter/issues/12319上讨论,但仍没有当前计划)
但是有一种不同类型的ListView支持scrollToIndex:
你可以像设置ListView一样设置它,工作方式相同,除了现在你可以访问ItemScrollController来执行以下操作:
jumpTo({index, alignment})
scrollTo({index, alignment, duration, curve})
简化示例:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: _myList.length,
itemBuilder: (context, index) {
return _myList[index];
},
)
_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));
scrollable_positioned_list
软件包是由google.dev发布的,但他们明确表示他们的软件包不是Google官方支持的产品。- 来源
ScrollablePositionedList
没有shrinkWrap
属性,不能与Slivers一起使用。 - Teh Sunn Liuscrollable_positioned_list
,只是因为我希望在未来(在我的产品发布之前)它能够获得一个ScrollController
(然后可以用来解决一些问题)。然而,另一个包scrollview_observer
虽然功能较少,但我发现其中的工具更加可靠。另外一种(hacky)添加ScrollController
的方法是使用CustomListView
,并将scrollable_positioned_list
作为其内部的sliver,同时将Ignore Pointer
设置为true,并将滚动物理效果设置为never
。这个方法是可行的[续下评论]。 - Matthew TrentCustomScrollView
,但在停止滚动约1.5秒后,它会错过内部的scrollable_positioned_list
滚动事件。它恰好错过了那一帧,然后在快速并发事件(同一帧中,如果您将一个scrollbar
附加到外部的CustomScrollView
,则会消失)。我还刚刚在我的问题上添加了一个赏金,询问scrollable_positioned_list
是否可以获得ScrollController
...希望这能产生一些积极的结果。 - Matthew Trent如果您的内容有固定的高度,则可以使用以下方法。
class HomePage extends StatelessWidget {
final ScrollController _controller = ScrollController();
final double _height = 100.0;
void _animateToIndex(int index) {
_controller.animateTo(
index * _height,
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_downward),
onPressed: () => _animateToIndex(10),
),
body: ListView.builder(
controller: _controller,
itemCount: 20,
itemBuilder: (_, i) {
return SizedBox(
height: _height,
child: Card(
color: i == 10 ? Colors.blue : null,
child: Center(child: Text('Item $i')),
),
);
},
),
);
}
}
WidgetsBinding.instance.addPostFrameCallback((_) => yourFunc(context));
。 - Tiago Santos你可以使用GlobalKey来访问buildercontext。
我在Scrollable
中使用了GlobalObjectKey
。
在ListView
的项目中定义GlobalObjectKey。
ListView.builder(
itemCount: category.length,
itemBuilder: (_, int index) {
return Container(
key: GlobalObjectKey(category[index].id),
您可以从任何地方导航到项目。InkWell(
onTap: () {
Scrollable.ensureVisible(GlobalObjectKey(category?.id).currentContext);
你可以使用 ensureVisible 属性来添加可滚动的动画效果。Scrollable.ensureVisible(
GlobalObjectKey(category?.id).currentContext,
duration: Duration(seconds: 1),// duration for scrolling time
alignment: .5, // 0 mean, scroll to the top, 0.5 mean, half
curve: Curves.easeInOutCubic);
ListView.builder
只有在视口中可见时才构建其子项,而视口外的子项尚未具备上下文。因此 Scrollable.ensureVisible
无法正常工作。我收到了来自视口外子项的上下文为空的错误。https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html - omega_mi对于那些正在尝试在CustomScrollView中跳转到小部件的人。
首先,将这个插件添加到您的项目中。
然后查看我下面的示例代码:
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
AutoScrollController _autoScrollController;
final scrollDirection = Axis.vertical;
bool isExpaned = true;
bool get _isAppBarExpanded {
return _autoScrollController.hasClients &&
_autoScrollController.offset > (160 - kToolbarHeight);
}
@override
void initState() {
_autoScrollController = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection,
)..addListener(
() => _isAppBarExpanded
? isExpaned != false
? setState(
() {
isExpaned = false;
print('setState is called');
},
)
: {}
: isExpaned != true
? setState(() {
print('setState is called');
isExpaned = true;
})
: {},
);
super.initState();
}
Future _scrollToIndex(int index) async {
await _autoScrollController.scrollToIndex(index,
preferPosition: AutoScrollPosition.begin);
_autoScrollController.highlight(index);
}
Widget _wrapScrollTag({int index, Widget child}) {
return AutoScrollTag(
key: ValueKey(index),
controller: _autoScrollController,
index: index,
child: child,
highlightColor: Colors.black.withOpacity(0.1),
);
}
_buildSliverAppbar() {
return SliverAppBar(
brightness: Brightness.light,
pinned: true,
expandedHeight: 200.0,
backgroundColor: Colors.white,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: BackgroundSliverAppBar(),
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(40),
child: AnimatedOpacity(
duration: Duration(milliseconds: 500),
opacity: isExpaned ? 0.0 : 1,
child: DefaultTabController(
length: 3,
child: TabBar(
onTap: (index) async {
_scrollToIndex(index);
},
tabs: List.generate(
3,
(i) {
return Tab(
text: 'Detail Business',
);
},
),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: _autoScrollController,
slivers: <Widget>[
_buildSliverAppbar(),
SliverList(
delegate: SliverChildListDelegate([
_wrapScrollTag(
index: 0,
child: Container(
height: 300,
color: Colors.red,
)),
_wrapScrollTag(
index: 1,
child: Container(
height: 300,
color: Colors.red,
)),
_wrapScrollTag(
index: 2,
child: Container(
height: 300,
color: Colors.red,
)),
])),
],
),
);
}
}
此解决方案优于其他答案,因为它不需要硬编码每个元素的高度。添加 ScrollPosition.viewportDimension
和 ScrollPosition.maxScrollExtent
将产生完整的内容高度。这可用于估算某个索引处元素的位置。如果所有元素都具有相同的高度,则估计是完美的。
// Get the full content height.
final contentSize = controller.position.viewportDimension + controller.position.maxScrollExtent;
// Index to scroll to.
final index = 100;
// Estimate the target scroll position.
final target = contentSize * index / itemCount;
// Scroll to that position.
controller.position.animateTo(
target,
duration: const Duration(seconds: 2),
curve: Curves.easeInOut,
);
一个完整的例子:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Test",
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final controller = ScrollController();
final itemCount = 1000;
return Scaffold(
appBar: AppBar(
title: Text("Flutter Test"),
),
body: Column(
children: [
ElevatedButton(
child: Text("Scroll to 100th element"),
onPressed: () {
final contentSize = controller.position.viewportDimension + controller.position.maxScrollExtent;
final index = 100;
final target = contentSize * index / itemCount;
controller.position.animateTo(
target,
duration: const Duration(seconds: 2),
curve: Curves.easeInOut,
);
},
),
Expanded(
child: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return ListTile(
title: Text("Item at index $index."),
);
},
itemCount: itemCount,
),
)
],
),
);
}
}
ScrollController
指定给你的listview,并在按钮单击时调用animateTo
方法。animateTo
的使用:class Example extends StatefulWidget {
@override
_ExampleState createState() => new _ExampleState();
}
class _ExampleState extends State<Example> {
ScrollController _controller = new ScrollController();
void _goToElement(int index){
_controller.animateTo((100.0 * index), // 100 is the height of container and index of 6th element is 5
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(),
body: new Column(
children: <Widget>[
new Expanded(
child: new ListView(
controller: _controller,
children: Colors.primaries.map((Color c) {
return new Container(
alignment: Alignment.center,
height: 100.0,
color: c,
child: new Text((Colors.primaries.indexOf(c)+1).toString()),
);
}).toList(),
),
),
new FlatButton(
// on press animate to 6 th element
onPressed: () => _goToElement(6),
child: new Text("Scroll to 6th element"),
),
],
),
);
}
}
Scrollable
上的ensureVisible
方法滚动到特定的子元素。我会尝试更改和更新答案,以展示正确和最佳的解决方案。 - Hemanth RajscrollDirection: Axis.horizontal
的 listView 上无法工作。 - BIS Tech如果您希望在构建视图树后立即将小部件显示出来,这里是 StatefulWidget
的解决方案。
通过扩展 Remi的答案 ,您可以使用以下代码实现:
class ScrollView extends StatefulWidget {
// widget init
}
class _ScrollViewState extends State<ScrollView> {
final dataKey = new GlobalKey();
// + init state called
@override
Widget build(BuildContext context) {
return Scaffold(
primary: true,
appBar: AppBar(
title: const Text('Home'),
),
body: _renderBody(),
);
}
Widget _renderBody() {
var widget = SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: 1160.0, width: double.infinity, child: new Card()),
SizedBox(height: 420.0, width: double.infinity, child: new Card()),
SizedBox(height: 760.0, width: double.infinity, child: new Card()),
// destination
Card(
key: dataKey,
child: Text("data\n\n\n\n\n\ndata"),
)
],
),
);
setState(() {
WidgetsBinding.instance!.addPostFrameCallback(
(_) => Scrollable.ensureVisible(dataKey.currentContext!));
});
return widget;
}
}
我使用 ListView
找到了一个完美的解决方案。
我忘记了这个解决方案来自哪里,所以我发布了我的代码。这个功劳属于别人。
21/09/22:编辑。我在这里发布了一个完整的示例,希望更加清晰。
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class CScrollToPositionPage extends StatefulWidget {
CScrollToPositionPage();
@override
State<StatefulWidget> createState() => CScrollToPositionPageState();
}
class CScrollToPositionPageState extends State<CScrollToPositionPage> {
static double TEXT_ITEM_HEIGHT = 80;
final _formKey = GlobalKey<FormState>();
late List _controls;
List<FocusNode> _lstFocusNodes = [];
final __item_count = 30;
@override
void initState() {
super.initState();
_controls = [];
for (int i = 0; i < __item_count; ++i) {
_controls.add(TextEditingController(text: 'hello $i'));
FocusNode fn = FocusNode();
_lstFocusNodes.add(fn);
fn.addListener(() {
if (fn.hasFocus) {
_ensureVisible(i, fn);
}
});
}
}
@override
void dispose() {
super.dispose();
for (int i = 0; i < __item_count; ++i) {
(_controls[i] as TextEditingController).dispose();
}
}
@override
Widget build(BuildContext context) {
List<Widget> widgets = [];
for (int i = 0; i < __item_count; ++i) {
widgets.add(TextFormField(focusNode: _lstFocusNodes[i],controller: _controls[i],));
}
return Scaffold( body: Container( margin: const EdgeInsets.all(8),
height: TEXT_ITEM_HEIGHT * __item_count,
child: Form(key: _formKey, child: ListView( children: widgets)))
);
}
Future<void> _keyboardToggled() async {
if (mounted){
EdgeInsets edgeInsets = MediaQuery.of(context).viewInsets;
while (mounted && MediaQuery.of(context).viewInsets == edgeInsets) {
await Future.delayed(const Duration(milliseconds: 10));
}
}
return;
}
Future<void> _ensureVisible(int index,FocusNode focusNode) async {
if (!focusNode.hasFocus){
debugPrint("ensureVisible. has not the focus. return");
return;
}
debugPrint("ensureVisible. $index");
// Wait for the keyboard to come into view
await Future.any([Future.delayed(const Duration(milliseconds: 300)), _keyboardToggled()]);
var renderObj = focusNode.context!.findRenderObject();
if( renderObj == null ) {
return;
}
var vp = RenderAbstractViewport.of(renderObj);
if (vp == null) {
debugPrint("ensureVisible. skip. not working in Scrollable");
return;
}
// Get the Scrollable state (in order to retrieve its offset)
ScrollableState scrollableState = Scrollable.of(focusNode.context!)!;
// Get its offset
ScrollPosition position = scrollableState.position;
double alignment;
if (position.pixels > vp.getOffsetToReveal(renderObj, 0.0).offset) {
// Move down to the top of the viewport
alignment = 0.0;
} else if (position.pixels < vp.getOffsetToReveal(renderObj, 1.0).offset){
// Move up to the bottom of the viewport
alignment = 1.0;
} else {
// No scrolling is necessary to reveal the child
debugPrint("ensureVisible. no scrolling is necessary");
return;
}
position.ensureVisible(
renderObj,
alignment: alignment,
duration: const Duration(milliseconds: 300),
);
}
}
mounted
应该用什么进行初始化? - Saugat Thapathis.setState()
的进一步代码。 - Chen Li Yong输出:
使用依赖项:
dependencies:
scroll_to_index: ^1.0.6
代码:(滚动将始终执行第6个索引小部件,因为它被硬编码添加到下面,请尝试使用您需要滚动到特定小部件的滚动索引)
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final scrollDirection = Axis.vertical;
AutoScrollController controller;
List<List<int>> randomList;
@override
void initState() {
super.initState();
controller = AutoScrollController(
viewportBoundaryGetter: () =>
Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
axis: scrollDirection);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
scrollDirection: scrollDirection,
controller: controller,
children: <Widget>[
...List.generate(20, (index) {
return AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: Container(
height: 100,
color: Colors.red,
margin: EdgeInsets.all(10),
child: Center(child: Text('index: $index')),
),
highlightColor: Colors.black.withOpacity(0.1),
);
}),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _scrollToIndex,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
// Scroll listview to the sixth item of list, scrollling is dependent on this number
Future _scrollToIndex() async {
await controller.scrollToIndex(6, preferPosition: AutoScrollPosition.begin);
}
}
SliverList
?这就是我所做的:https://dev59.com/NLzpa4cB1Zd3GeqPVv5p - user1506104