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",),