Flutter:在ListView中滚动到一个小部件

187
如何在ListView中滚动到特定的小部件? 例如,如果我按下特定按钮,我想自动滚动到ListView中的某个Container
ListView(children: <Widget>[
  Container(...),
  Container(...), #scroll for example to this container 
  Container(...)
]);

使用 SliverList?这就是我所做的:https://dev59.com/NLzpa4cB1Zd3GeqPVv5p - user1506104
使用:https://pub.dev/packages/hidable - theiskaa
23个回答

251

目前来看,最简单的解决方案是使用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.ensureVisibleListView一起使用。


7
如所述,这个解决方案适用于短列表,并且作为列实现很好。如何修改才能使其与包含SliverLists的CustomScrollView一起使用? - Amit Kotlovski
2
ListViewSingleChildScrollView是完全不同的东西。如果使用情况适合于SingleChildScrollView,那么这个问题本来就不存在。 - Sarp Başaraner
15
如果构建后需要滚动,则可以使用WidgetsBinding.instance.addPostFrameCallback((_) => Scrollable.ensureVisible(dataKey.currentContext)) - ych
1
@SarpBaşaraner,他们可能是这样的,但是这个问题实际上帮助我理解了SingleChildScrollView,所以这是一个非常有用的答案! - cs guy
4
请提供一个完整的代码示例,展示如何实现Scrollable.ensureVisibleListView的配合使用所需的“更多工作”! - Karolina Hagegård
显示剩余6条评论

87

不幸的是,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官方支持的产品。- 来源

1
这个完美地运作。如果所有项目的尺寸相同,则其他答案都可以,否则滚动将不精确。 - Paul Kitatta
4
不错的回答。然而,ScrollablePositionedList没有shrinkWrap属性,不能与Slivers一起使用。 - Teh Sunn Liu
1
嗨@Daniel,如上面的答案所述(https://dev59.com/PVUM5IYBdhLWcg3wOeJY#49154882)。即使在Slivers中,使用Scrollable.ensureVisible(widgetKey.currentContext)也可以滚动小部件。您需要做的就是为您的小部件设置全局键,并在要滚动到的小部件的键上调用Scrollable.ensureVisible。为了使其工作,您的ListView应该是有限的对象列表。如果您正在使用ListView.builder。我建议您将physics: NeverScrollableScrollPhysics和shrinkWrap设置为true。 - Teh Sunn Liu
1
@DennisAshford,我最终选择了scrollable_positioned_list,只是因为我希望在未来(在我的产品发布之前)它能够获得一个ScrollController(然后可以用来解决一些问题)。然而,另一个包scrollview_observer虽然功能较少,但我发现其中的工具更加可靠。另外一种(hacky)添加ScrollController的方法是使用CustomListView,并将scrollable_positioned_list作为其内部的sliver,同时将Ignore Pointer设置为true,并将滚动物理效果设置为never。这个方法是可行的[续下评论]。 - Matthew Trent
1
因为它允许您跟踪外部的CustomScrollView,但在停止滚动约1.5秒后,它会错过内部的scrollable_positioned_list滚动事件。它恰好错过了那一帧,然后在快速并发事件(同一帧中,如果您将一个scrollbar附加到外部的CustomScrollView,则会消失)。我还刚刚在我的问题上添加了一个赏金,询问scrollable_positioned_list是否可以获得ScrollController...希望这能产生一些积极的结果。 - Matthew Trent
显示剩余6条评论

54

截图(固定高度内容)

在此输入图片描述


如果您的内容有固定的高度,则可以使用以下方法。

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

7
我曾尝试在一个高度不同的列表上使用这个方法,但它无法滚动到精确的位置。你应该加上一句话说明它最适用于具有相同高度(如果是水平滚动,则为相同宽度)的列表。我最终使用了@TWL的解决方案,该解决方案使用了项目的索引。 - Paul Kitatta
如果你不想用一个按钮来触发动画,而是希望在小部件建立后立即执行动画,那么请在itemBuilder中添加WidgetsBinding.instance.addPostFrameCallback((_) => yourFunc(context)); - Tiago Santos
@CopsOnRoad 有没有办法在滚动时查找索引?我的意思是向上和向下滚动,找到顶部的索引? - BIS Tech
您能建议如何实现吗?https://www.youtube.com/watch?v=LrOR5QOCHBI - BIS Tech
当你尝试滚动到最新的项目时会发生什么?滚动条会尝试继续滚动,直到最新的项目在顶部,这显然是不可能的,这会导致多余的“额外滚动”效果。 - idish
显示剩余6条评论

34

你可以使用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中突出显示项目列表中的一个项目,它完美地工作。我使用滚动控制器一段时间,但最好的结果显然是使用您的方法。 - beauchette
3
我认为这是最好的解决方案。 - Onur Kağan Aldemir
1
对我来说,这是最好的答案。谢谢你,朋友! - Raphael Souza
4
它对于小列表完美地工作,但对于大列表来说,由于listView builder没有渲染项,它的效果不太好,除非该项被渲染。 - Abdullah Qasemi
3
ListView.builder 只有在视口中可见时才构建其子项,而视口外的子项尚未具备上下文。因此 Scrollable.ensureVisible 无法正常工作。我收到了来自视口外子项的上下文为空的错误。https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html - omega_mi
对我来说,甚至在SingleChildScrollView内部也完美运作。 - undefined

30

对于那些正在尝试在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,
                )),
          ])),
        ],
      ),
    );
  }
}

是的,这只是一个例子,请动动脑筋让这个想法变成现实。enter image description here


@AXE,有没有办法让应用程序栏对滚动作出反应?我正在尝试制作这个:https://twitter.com/maurodibert/status/1366341648343568389 谢谢! - Mau Di Bert
@AXE,这里可能是解决方案:https://titanwolf.org/Network/Articles/Article?AID=f85aaeb2-8233-45d0-899a-25464e35fba5#gsc.tab=0 - Mau Di Bert
@AXE 抱歉,但它展示了如何实现相同的功能,但没有同步。 - Mau Di Bert
需要注意的是,您不必将每个小部件都包装在“_wrapScrollTag()”中,只需将要滚动到的小部件包装起来,它就会按预期工作。 - Lalit Fauzdar

23

此解决方案优于其他答案,因为它不需要硬编码每个元素的高度。添加 ScrollPosition.viewportDimensionScrollPosition.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,
);

一个完整的例子:

用户点击按钮以滚动到长列表的第100个元素

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


这是一种非常酷的临时方法,可以滚动浏览具有相似或几乎相似小部件的列表视图。 - TheCoder

11
你可以将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"),
          ),
        ],
      ),
    );
  }
}

35
太复杂了,特别是对于未知大小的元素。 - Rémi Rousselet
不确定在大小未知的情况下是否有办法滚动到特定的小部件。如果可以通过轻微更改实现,请随意编辑我的答案。如果不能,请发布一个答案,以便我也可以了解其他方法。谢谢@Darky - Hemanth Raj
9
有人找到了处理高度不同的项目的方法吗?这对Flutter来说是个致命问题 :( - Christine
嗨@StanMots,我在Flutter还处于初始alpha版本时回答了这个问题。现在已经有了很多改进,我们可以使用Scrollable上的ensureVisible方法滚动到特定的子元素。我会尝试更改和更新答案,以展示正确和最佳的解决方案。 - Hemanth Raj
1
谢谢,@HemanthRaj 这很简单明了,对我有用。 - irzum shahid
@HemanthRaj 在 scrollDirection: Axis.horizontal 的 listView 上无法工作。 - BIS Tech

8

如果您希望在构建视图树后立即将小部件显示出来,这里是 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;
  }
}


1
这个答案对我非常有效。谢谢。 - MAlhamry
为了获得良好的动画效果,您还可以添加(持续时间),例如:Scrollable.ensureVisible(dataKey.currentContext!, duration: Duration(seconds: 1))。 - MAlhamry

6

我使用 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),
    );

}

}

你好。能否添加一些解释,比如代码的作用是什么?或者截图?这将非常有帮助。谢谢。 - Teh Sunn Liu
@anakin.jin,mounted应该用什么进行初始化? - Saugat Thapa
@SaugatThapa mounted 是一个已经存在于有状态小部件上的变量,用于指示小部件是否已连接并可操作(例如已弹出)。您只需要检查它是 true 还是 false。如果为 false,则防止任何可能导致 this.setState() 的进一步代码。 - Chen Li Yong
1
这不是完美的...这是这里最糟糕的解决方案 - Mohammed Hamdan

5

输出:

使用依赖项:

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

1
谢谢!这个解决方案完美地适用于我的使用情况,因为我有一些与滚动控制器相关的操作,而之前回答中提到的另一个库(scrollable_positioned_list)没有提供解决方案。 - pgzm29
如何在没有浮动按钮的情况下实现相同的功能?比如当用户向左或向右滑动时? - Md. Kamrul Amin
@KamrulHasanJony:尝试使用横向的Listview,这应该会起作用。 - Jitesh Mohite
尝试过了。用户可以在两个列表项之间停止。因此,为了解决这个问题,我使用了PageView Builder,它的效果非常好。 - Md. Kamrul Amin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接