Listview滚动到小部件

时间:2018-03-07 13:25:46

标签: flutter

如何滚动到列表视图中的特殊小部件? 例如,如果我按下特定按钮,我想自动滚动到ListView中的某个Container。

new ListView(children: <Widget>[
             new Container(...),
             new Container(...), #scroll for example to this container 
             new Container(...)
      ]);

13 个答案:

答案 0 :(得分:10)

到目前为止,最简单的解决方案是使用Scrollable.ensureVisible(context)。因为它为您完成所有工作并使用任何小部件大小。使用GlobalKey获取上下文。

问题是ListView不会呈现不可见的项目。这意味着您的目标最有可能不会构建。这意味着您的目标将没有context;在没有更多工作的情况下阻止您使用该方法。

最后,最简单的解决方案是将ListView替换为SingleChilScrollView,并将您的孩子换成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.ensureVisibleListView合作;虽然这需要更多的工作。

答案 1 :(得分:6)

并非所有超级英雄都披着斗篷。

对于试图在 CustomScrollView 中跳转到窗口小部件的人。 首先,将此plugin添加到您的项目中。

然后在下面查看我的示例代码:

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,
                )),
          ])),
        ],
      ),
    );
  }
}

是的,这只是一个例子,用你的大脑使这个想法成为现实 enter image description here

答案 2 :(得分:2)

不幸的是,ListView没有对scrollToIndex()函数的内置方法。您必须开发自己的方法来测量animateTo()jumpTo()的元素偏移量,或者可以搜索这些建议的解决方案/插件或其他类似flutter ListView scroll to index not available的帖子

(自2017年以来,flutter/issues/12319讨论了一般的scrollToIndex问题,但目前尚无计划)


但是还有一种不支持scrollToIndex的ListView:

您可以像设置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));

(请注意,该库是由Google开发的,而不是由Flutter核心团队开发的。)

答案 3 :(得分:2)

我使用ListView找到了一个完美的解决方案。
我忘记了解决方案的来源,所以我发布了代码。此信用属于其他信用。

///
/// 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),
    );

}

答案 4 :(得分:2)

输出:

enter image description here

使用依赖项:

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);
  }
}

答案 5 :(得分:1)

您只需在列表视图中指定ScrollController,然后点击按钮即可调用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"),
          ),
        ],
      ),
    );
  }
}

答案 6 :(得分:1)

如果您想让小部件在构建视图树后立即可见,这里是 Option::as_mut 的解决方案。

通过扩展 Remi's 答案,您可以使用以下代码实现:

StatefulWidget

答案 7 :(得分:0)

加载完成后即可使用controller.jumpTo(100)

答案 8 :(得分:0)

截屏:

enter image description here


对于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);
}

答案 9 :(得分:0)

加上 Rémi Rousselet 的回答,

如果您需要通过添加键盘弹出窗口滚动到结束滚动位置,这可能会被键盘隐藏。另外你可能会注意到滚动键盘弹出时的动画有点不一致(键盘弹出时有额外的动画),有时表现得很奇怪。在这种情况下,请等待键盘完成动画(ios 为 500 毫秒)。

BuildContext context = key.currentContext;
  Future.delayed(const Duration(milliseconds: 650), () {
    Scrollable.of(context).position.ensureVisible(
        context.findRenderObject(),
        duration: const Duration(milliseconds: 600));
  });

答案 10 :(得分:0)

我在这里发布了一个解决方案,其中列表视图将左右滚动 100 像素。您可以根据您的要求更改该值。对于想要双向滚动列表的人来说可能会有所帮助

import 'package:flutter/material.dart';

class HorizontalSlider extends StatelessWidget {
 HorizontalSlider({Key? key}) : super(key: key);

// Dummy Month name
List<String> monthName = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"July",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
 ];
ScrollController slideController = new ScrollController();

@override
Widget build(BuildContext context) {
 return Container(
  child: Flex(
    direction: Axis.horizontal,
    crossAxisAlignment: CrossAxisAlignment.center,
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      InkWell(
        onTap: () {
          // Here monthScroller.position.pixels represent current postion 
          // of scroller
           slideController.animateTo(
            slideController.position.pixels - 100, // move slider to left
             duration: Duration(
              seconds: 1,
            ),
            curve: Curves.ease,
          );
        },
        child: Icon(Icons.arrow_left),
      ),
      Container(
        height: 50,
        width: MediaQuery.of(context).size.width * 0.7,
        child: ListView(
          scrollDirection: Axis.horizontal,
          controller: slideController,
          physics: ScrollPhysics(),
          children: monthName
              .map((e) => Padding(
                    padding: const EdgeInsets.all(12.0),
                    child: Text("$e"),
                  ))
              .toList(),
        ),
      ),
      GestureDetector(
        onTap: () {
          slideController.animateTo(
            slideController.position.pixels +
                100, // move slider 100px to right
            duration: Duration(
              seconds: 1,
            ),
            curve: Curves.ease,
          );
        },
        child: Icon(Icons.arrow_right),
      ),
    ],
  ),
);
 }
 }

答案 11 :(得分:-1)

  1. 在项目列表中的特定索引处实现初始滚动
  2. 点击浮动操作按钮,您将滚动到项目列表中的索引 10
    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);
    }

答案 12 :(得分:-1)

只需使用页面视图控制器。 示例:

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