Flutter如何滚动到SliverList中的特定项目位置?

Posted

技术标签:

【中文标题】Flutter如何滚动到SliverList中的特定项目位置?【英文标题】:Flutter how to scroll to a specific item position in SliverList? 【发布时间】:2020-06-17 02:36:04 【问题描述】:

我有一个SliverListSliverChildBuilderDelegate,我的问题是我无法将ScrollController 设置为SliverList,因为我想控制列表的滚动,我正在寻找滚动到列表中的特定项目(这是非常大的)和正常的ListView,这项工作很容易,我想知道为什么SliverList 中没有controller 属性,比如ListView

注意我知道CustomScrollViewcontroller 属性并且我已经测试过,但这将控制整个视口而不仅仅是列表

 @override
  Widget build(BuildContext context) 
    MainState m = Provider.of<MainState>(context, listen: false);
    return DefaultTabController(
      length: m.itemCategoryList.length,
      child: Scaffold(
          // appBar: _createAppBar(),
          body: Builder(builder: (context) => _createBody(context))),
    );
  

  AppBar _createAppBar() 
    MainState m = Provider.of<MainState>(context);

    return AppBar(
      title: Text(
        m.selectedPartner.fullname,
        style: Theme.of(context).textTheme.display3,
      ),
      leading: IconButton(
        icon: Icon(Icons.arrow_back),
        onPressed: () 
          Navigator.pop(context);
        ,
      ),
    );
  

  _createBody(BuildContext context) 
    MainState m = Provider.of<MainState>(context);
    return CustomScrollView(
      slivers: <Widget>[
        SliverAppBar(
          titleSpacing: 0.0,
          elevation: 20.0,
          title: Container(
            decoration: BoxDecoration(color: Colors.black.withOpacity(0.3)),
            child: Column(
              children: <Widget>[
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: <Widget>[
                    IconButton(
                      icon: Icon(Icons.arrow_back),
                      onPressed: () 
                        Navigator.pop(context);
                      ,
                    ),
                    Spacer(
                      flex: 2,
                    ),
                    IconButton(
                      icon: Icon(Icons.info),
                      onPressed: () ,
                    ),
                    IconButton(
                      icon: Icon(Icons.search),
                      onPressed: () ,
                    ),
                  ],
                ),
              ],
            ),
          ),
          centerTitle: false,
          automaticallyImplyLeading: false,
          pinned: true,
          bottom: PreferredSize(
            // Add this code
            preferredSize: Size.fromHeight(10.0), // Add this code
            child: Text(''), // Add this code
          ),
          expandedHeight: 250,
          flexibleSpace: FlexibleSpaceBar(
              centerTitle: true,
              collapseMode: CollapseMode.none,
              background: Stack(
                children: <Widget>[
                  Image.network(
                    m.selectedPartner.imagepath,
                    fit: BoxFit.cover,
                    height: 300.0,
                    width: MediaQuery.of(context).size.width,
                    color: Colors.black.withOpacity(0.6),
                    colorBlendMode: BlendMode.darken,
                  ),
                  Align(
                    alignment: Alignment.center,
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        Text(
                          m.selectedPartner.fullname,
                          style: Theme.of(context)
                              .textTheme
                              .headline
                              .copyWith(color: Colors.white),
                        ),
                        Text(
                          m.selectedPartner.categorytexts,
                          style: Theme.of(context)
                              .textTheme
                              .title
                              .copyWith(fontSize: 17.0, color: Colors.white),
                        ),
                      ],
                    ),
                  ),
                  Align(
                    alignment: Alignment.bottomCenter,
                    child: Container(
                      height: 30.0,
                      decoration: BoxDecoration(color: Colors.white),
                      child: Row(
                        children: <Widget>[
                          Text(
                              "Delivery time :" +
                                  m.selectedPartner.deliverytime.toString() +
                                  " mins",
                              style: Theme.of(context).textTheme.subtitle)
                        ],
                      ),
                    ),
                  )
                ],
              )),
        ),
        SliverPersistentHeader(
          delegate: _SliverAppBarDelegate(
            TabBar(
              isScrollable: true,
              labelColor: Colors.black87,
              unselectedLabelColor: Colors.black,
              tabs: m.itemCategoryList
                  .map((ele) => Tab(
                        text: ele.itemcategoryname,
                      ))
                  .toList(),
            ),
          ),
          pinned: true,
        ),
//=========I want to scroll to a specific position in this list
        GroupedSliverList(
            elements: m.itemList,
            groupBy: (ItemModel item) => item.itemcategory_id,
            groupSeparatorBuilder: (i, item) => _createGroupItem(i, item),
            itemBuilder: (_, item) => _createItem(item)),
      ],
    );
  


  _createGroupItem(int categId, ItemModel item) 
    MainState m = Provider.of<MainState>(context, listen: false);
    ItemCategoryModel categModel =
        m.itemCategoryList.firstWhere((ele) => ele.id == categId);
    return Container(
      child: Text(
        categModel.itemcategoryname,
        style: Theme.of(context).textTheme.headline,
      ),
    );
  

 

  _createItem(ItemModel item) 
    return Card(
      elevation: 3.0,
      child: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Row(
          children: <Widget>[
            CircleAvatar(
              radius: 30.0,
              backgroundImage: NetworkImage(item.imagepath),
            ),
            SizedBox(
              width: 18.0,
            ),
            _createDetailColumn(item)
          ],
        ),
      ),
    );
  

  _createDetailColumn(ItemModel item) 
    return Expanded(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Flexible(
              flex: 1,
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: Text(
                      item.itemname,
                      style: Theme.of(context).textTheme.title,
                    ),
                  ),
                ],
              )),
          SizedBox(
            height: 5.0,
          ),
          Flexible(
            flex: 1,
            child: Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    item.description,
                    style: Theme.of(context).textTheme.subtitle,
                  ),
                ),
              ],
            ),
          ),
          SizedBox(
            height: 5.0,
          ),
          SizedBox(
            height: 8.0,
          ),
          Row(
            children: <Widget>[
              Container(
                decoration: BoxDecoration(
                    border: Border.all(color: Colors.green, width: 1.0),
                    borderRadius: BorderRadius.circular(10.0)),
                child: Padding(
                  padding: const EdgeInsets.all(6.0),
                  child: Text(item.price.toString() + " L.E.",
                      style: Theme.of(context)
                          .textTheme
                          .subtitle
                          .copyWith(color: Colors.green, fontSize: 16.0)),
                ),
              ),
            ],
          )
        ],
      ),
    );
  


class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate 
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @override
  double get minExtent => _tabBar.preferredSize.height;
  @override
  double get maxExtent => _tabBar.preferredSize.height;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) 
    return new Container(
      color: Colors.yellow,
      child: _tabBar,
    );
  

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) 
    return false;
  

【问题讨论】:

自定义 SliverList 哪些组列表项 你做到了吗? 我也有同样的问题。有人找到解决办法了吗? 【参考方案1】:

你可以用这个:

    为您的特定项目位置创建一个全局键,如下所示final dataKey = new GlobalKey(); 将键 key: dataKey 添加到此特定位置 添加使页面滚动到您的按钮Scrollable.ensureVisible(dataKey.currentContext)的命令

这是滚动到最后一项的完整代码:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Scroll To Index Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SilverAppBarExample(),
    );
  




class SilverAppBarExample extends StatelessWidget 
  final dataKey = new GlobalKey();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Container(
        // width: MediaQuery.of(context).size.width,
        // height: MediaQuery.of(context).size.height,
        child: CustomScrollView(
          slivers: <Widget>[
            SliverAppBar(
              leading: IconButton(
                  icon: Icon(Icons.filter_1),
                  onPressed: () 
                    // Do something
                  ),
              expandedHeight: 220.0,
              floating: true,
              pinned: true,
              snap: true,
              elevation: 50,
              backgroundColor: Colors.pink,
              flexibleSpace: FlexibleSpaceBar(
                centerTitle: true,
                title: Text('Title',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 16.0,
                    )),
                background: Image.network(
                  'https://media-exp1.licdn.com/dms/image/C4D03AQEs7z0qHy2IWA/profile-displayphoto-shrink_400_400/0/1587196263121?e=1611792000&v=beta&t=bGjaklbEtTDYOFk5UvGLOTO5N3pSvIJtq6javEp32lU',
                  fit: BoxFit.cover,
                ),
              ),
            ),
            new SliverList(
              delegate: new SliverChildListDelegate([
                Column(
                  children: [
                    _buildList(50),
                    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"),
      ),
    );
  

  Widget _buildList(int count) 
    List<Widget> listItems = List();

    for (int i = 0; i < count; i++) 
      listItems.add(
        new Padding(
          padding: new EdgeInsets.all(20.0),
          child: new Text(
            'Item $i.toString()',
            style: new TextStyle(fontSize: 25.0),
          ),
        ),
      );
    

    return Column(
      children: listItems,
    );
  

滚动前的页面:

滚动后的页面:

【讨论】:

这很有帮助。但并不完全满足能够按照操作要求滚动到列表中的特定项目 你在这里滥用 SliverList。您的实现不是虚拟的,在大多数情况下不起作用。【参考方案2】:

您是否尝试过使用infinite listview? 我要自己试试这个。看起来很有希望。

import 'package:flutter/material.dart';
import 'package:infinite_listview/infinite_listview.dart';

void main() => runApp(ExampleApp());

class ExampleApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ExampleScreen(),
    );
  


class ExampleScreen extends StatefulWidget 
  @override
  _ExampleScreenState createState() => _ExampleScreenState();


class _ExampleScreenState extends State<ExampleScreen> 
  final InfiniteScrollController _infiniteController = InfiniteScrollController(
    initialScrollOffset: 0.0,
  );

  @override
  Widget build(BuildContext context) 
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Example'),
          actions: <Widget>[
            IconButton(
              icon: const Icon(Icons.arrow_downward),
              onPressed: () 
                _infiniteController.animateTo(_infiniteController.offset + 2000.0,
                    duration: const Duration(milliseconds: 250), curve: Curves.easeIn);
              ,
            ),
            IconButton(
              icon: const Icon(Icons.arrow_upward),
              onPressed: () 
                _infiniteController.animateTo(_infiniteController.offset - 2000.0,
                    duration: const Duration(milliseconds: 250), curve: Curves.easeIn);
              ,
            ),
          ],
          bottom: const TabBar(
            tabs: <Widget>[
              Tab(text: 'First'),
              Tab(text: 'Second'),
              Tab(text: 'Third'),
            ],
          ),
        ),
        body: TabBarView(
          children: <Widget>[
            _buildTab(0),
            _buildTab(1),
            _buildTab(2),
          ],
        ),
      ),
    );
  

  Widget _buildTab(int tab) 
    return InfiniteListView.separated(
      key: PageStorageKey(tab),
      controller: _infiniteController,
      itemBuilder: (BuildContext context, int index) 
        return Material(
          child: InkWell(
            onTap: () ,
            child: ListTile(
              title: Text('Tab $tab Item #$index'),
              subtitle: Text('Subtitle $index'),
              trailing: const Icon(Icons.chevron_right),
            ),
          ),
        );
      ,
      separatorBuilder: (BuildContext context, int index) => const Divider(height: 2.0),
      anchor: 0.5,
    );
  

【讨论】:

【参考方案3】:

您可以使用此插件 scrollable_positioned_list 来执行滚动事件,但不能将其与 silverList 一起使用

【讨论】:

以上是关于Flutter如何滚动到SliverList中的特定项目位置?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter SliverAppBar 和 SliverList 有不同的滚动条

如何为 Flutter SliverList 中的元素设置动画?

在 Flutter 中通过拖放重新排序 SliverList 中的项目

按下 Tab 时自动滚动到 SliverList 项目之一

Flutter:将新元素添加到列表时保持滚动偏移

如何在 SliverList 中显示滚动条?