Flutter 颤振:滚动到ListView中的小部件
如何滚动到Flutter 颤振:滚动到ListView中的小部件,flutter,listview,dart,flutter-layout,flutter-container,Flutter,Listview,Dart,Flutter Layout,Flutter Container,如何滚动到列表视图中的特殊小部件? 例如,如果我按下特定按钮,我想自动滚动到列表视图中的某个容器 ListView(children: <Widget>[ Container(...), Container(...), #scroll for example to this container Container(...) ]); ListView(子项:[ 容器(…), 容器(…),#例如滚动到此容器 容器(…) ]); 您只需在listview中指定一个方法,并在
列表视图中的特殊小部件?
例如,如果我按下特定按钮,我想自动滚动到列表视图中的某个容器
ListView(children: <Widget>[
Container(...),
Container(...), #scroll for example to this container
Container(...)
]);
ListView(子项:[
容器(…),
容器(…),#例如滚动到此容器
容器(…)
]);
您只需在listview中指定一个方法,并在单击按钮时调用该方法即可
演示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"),
),
],
),
);
}
}
类示例扩展了StatefulWidget{
@凌驾
_ExampleState createState()=>新建_ExampleState();
}
类_示例状态扩展状态{
ScrollController _controller=新的ScrollController();
void _goToElement(整数索引){
_controller.animateTo((100.0*索引),//100是容器的高度,第6个元素的索引是5
持续时间:常量持续时间(毫秒:300),
曲线:Curves.easeOut);
}
@凌驾
小部件构建(构建上下文){
归还新脚手架(
appBar:新的appBar(),
正文:新栏目(
儿童:[
新扩展(
子:新列表视图(
控制器:_控制器,
子类:颜色。原色。贴图((颜色c){
退回新货柜(
对齐:对齐.center,
高度:100.0,
颜色:c,
子:新文本((颜色.原色.索引of(c)+1).toString()),
);
}).toList(),
),
),
新扁平按钮(
//按“动画到第6个元素”
按下时:()=>\u goToElement(6),
子项:新文本(“滚动到第6个元素”),
),
],
),
);
}
}
到目前为止,最简单的解决方案是使用。因为它可以为您做任何事情,并且可以使用任何小部件大小。使用GlobalKey
获取上下文
问题是ListView
不会呈现不可见的项目。这意味着你的目标很可能根本就不会建立。这意味着您的目标将没有上下文
;阻止您在没有更多工作的情况下使用该方法
最后,最简单的解决方案是将您的列表视图
替换为SingleChildScrollView
,并将您的孩子包装到列中
。例如:
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"),
),
);
}
}
class ScrollView扩展了无状态小部件{
final dataKey=new GlobalKey();
@凌驾
小部件构建(构建上下文){
归还新脚手架(
小学:对,
appBar:新的appBar(
标题:常量文本(“主页”),
),
正文:新的SingleChildScrollView(
子:新列(
儿童:[
新大小的盒子(高度:160.0,宽度:double.infinity,子项:新卡()),
新大小的盒子(高度:160.0,宽度:double.infinity,子项:新卡()),
新大小的盒子(高度:160.0,宽度:double.infinity,子项:新卡()),
//目的地
新卡(
key:dataKey,
子项:新文本(“数据\n\n\n\n\n数据”),
)
],
),
),
底部导航栏:新建升起按钮(
onPressed:()=>可滚动。可确保可修改(dataKey.currentContext),
子项:新文本(“滚动到数据”),
),
);
}
}
<强>注释< /强>:虽然这允许很容易滚动到所需的项目,但只针对小的预定义列表考虑此方法。对于较大的列表,您将遇到性能问题
但是可以使滚动。确保可修改
使用列表视图
;尽管这需要更多的工作。您可以在加载完成后使用controller.jumpTo(100)如果ListView中的小部件都具有相同的高度,您可以这样实现它:
屏幕截图:
dependencies:
scroll_to_index: ^1.0.6
对于ListView
,您可以尝试这样做,下面的代码将动画到第10个索引
class HomePage extends StatelessWidget {
final _controller = ScrollController();
final _height = 100.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => _animateToIndex(10),
child: Icon(Icons.arrow_downward),
),
body: ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _height,
child: Card(child: Center(child: Text("Item $i"))),
),
),
);
}
_animateToIndex(i) => _controller.animateTo(_height * i, duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn);
}
不幸的是,ListView没有内置的scrollToIndex()函数方法。你必须为animateTo()
或jumpTo()
开发自己的方法来测量该元素的偏移量,或者你可以通过这些建议的解决方案/插件或其他类似的帖子进行搜索
(自2017年起,通用scrollToIndex问题已在上讨论,但目前仍没有计划)
但有一种不同的ListView支持scrollToIndex:
-
- 依赖关系:
您的设置与ListView完全相同,工作原理也相同,只是您现在可以访问一个:
跳转到({index,alignment})
scrollTo({索引、对齐、持续时间、曲线})
简化示例:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: _myList.length,
itemBuilder: (context, index) {
return _myList[index];
},
)
_scrollController.scrollTo(index: 150, duration: Duration(seconds: 1));
(请注意,这个库是由谷歌开发的,但不是由核心颤振团队开发的。)我使用列表视图找到了一个完美的解决方案
我忘记了解决方案的来源,所以我发布了我的代码。这项信贷属于另一项
///
/// This routine waits for the keyboard to come into view.
/// In order to prevent some issues if the Widget is dismissed in the
/// middle of the loop, we need to check the "mounted" property
///
/// This method was suggested by Peter Yuen (see discussion).
///
Future<Null> _keyboardToggled() async {
if (mounted){
EdgeInsets edgeInsets = MediaQuery.of(context).viewInsets;
while (mounted && MediaQuery.of(context).viewInsets == edgeInsets) {
await new Future.delayed(const Duration(milliseconds: 10));
}
}
return;
}
///for every items in ListView i assigned a FocusNode object and after got focus, invokes blow function to adjust position.
Future<Null> _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([new Future.delayed(const Duration(milliseconds: 300)), _keyboardToggled()]);
var renderObj = focusNode.context.findRenderObject();
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);
assert(scrollableState != null);
// 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: Duration(milliseconds: 300),
);
}
///
///此例程等待键盘进入视图。
///为了防止在关闭窗口小部件时出现一些问题
///在循环的中间,我们需要检查“mounted”属性
///
///此方法由Peter Yuen提出(见讨论)。
///
Future\u keyboardToggled()异步{
如果(已安装){
EdgeInsets EdgeInsets=MediaQuery.of(context).viewInsets;
while(mounted&&MediaQuery.of(context.viewInsets==edgeInsets){
等待新的未来。延迟(常量持续时间(毫秒:10));
}
}
返回;
}
///对于ListView中的每个项目,我都分配了一个FocusNode对象,在获得焦点后,调用blow函数来调整位置。
未来可修改(int索引,FocusNode FocusNode)异步{
如果(!focusNode.hasFocus){
debugPrint(“EnsureRevisible.has not the focus.return”);
返回;
dependencies:
scroll_to_index: ^1.0.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);
}
}
BuildContext context = key.currentContext;
Future.delayed(const Duration(milliseconds: 650), () {
Scrollable.of(context).position.ensureVisible(
context.findRenderObject(),
duration: const Duration(milliseconds: 600));
});
class HomePage extends StatelessWidget {
final _controller = ScrollController();
final _height = 100.0;
@override
Widget build(BuildContext context) {
// to achieve initial scrolling at particular index
SchedulerBinding.instance.addPostFrameCallback((_) {
_scrollToindex(20);
});
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => _scrollToindex(10),
child: Icon(Icons.arrow_downward),
),
body: ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _height,
child: Card(child: Center(child: Text("Item $i"))),
),
),
);
}
// on tap, scroll to particular index
_scrollToindex(i) => _controller.animateTo(_height * i,
duration: Duration(seconds: 2), curve: Curves.fastOutSlowIn);
}
var controller = PageController();
ListView.builder(
controller: controller,
itemCount: 15,
itemBuilder: (BuildContext context, int index) {
return children[index);
},
),
ElevatedButton(
onPressed: () {
controller.animateToPage(5, //any index that you want to go
duration: Duration(milliseconds: 700), curve: Curves.linear);
},
child: Text(
"Contact me",),