# 13.ListView滑动到指定位置

### 前言

在进行列表视图的开发场景中，通过索引快速滑动到指定 section 是常见的功能。那么在 flutter 中我们该如何实现这种功能呢？ 本文就将以联系人页面为例进行讲解。

![2](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-67e8c733aedabdea6ea4eb6b394eda18422eb31c%2F13-02.GIF?alt=media)

### 分析ListView

在 iOS 开发中我们常使用 `open func scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool)` 这类方法来实现 CollectionView 和 TableView 下的滚动到指定位置的效果。

但 flutter 的 ListView 并不含有这种分组的概念，也没有提供这种便利的方法，但是有基础API可以为我们提供一些思路。

#### ScrollController

我们可以在为 ListView 绑定一个 ScrollController ，它的作用可以理解为我们进行 iOS 开发中的 相关代理，但是他的作用会更富一些。

![1](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-6a6f1e7e85b1893334265c8b52ebb609e50b4130%2F13-01.png?alt=media)

为来滚动到指定位置，这里提供了两个 API，更具体的说明可以点进头文件查看注释，这里就不再赘述。

**animateTo**

有动画

```
  Future<void> animateTo(
    double offset, {
    required Duration duration,
    required Curve curve,
  }) async {
    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
    await Future.wait<void>(<Future<void>>[
      for (int i = 0; i < _positions.length; i += 1) _positions[i].animateTo(offset, duration: duration, curve: curve),
    ]);
  }
```

**jumpTo**

无动画

```
  void jumpTo(double value) {
    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
    for (final ScrollPosition position in List<ScrollPosition>.from(_positions))
      position.jumpTo(value);
  }
```

这里为了实现通讯录滑动到组头部的效果，选择使用 `jumpTo`。

#### 提前计算好每组的偏移量

```
class _ContactsPageState extends State<ContactsPage> {
  final List<Friends> _contacts = [];// 联系人数组
  final List<Friends> _systems = []; // 顶部系统入口数组
  final List<String> _indexs = []; // 索引
  ScrollController _scroller = ScrollController();
  Map<int, double> _indexOffsets = {}; // 偏移量Map
  
  @override
  void initState() {
    super.initState();

    double offsetY = 0;

    _contacts
      ..addAll(friendsData)
      ..addAll(friendsData)
      ..sort((a, b) {
        if (a.indexLetter == null || b.indexLetter == null) {
          return 0;
        }
        return a.indexLetter!.compareTo(b.indexLetter!);
      });

    _systems
        .addAll(friendsHeaderData);

    offsetY = ContactCell.baseCellHeight * friendsHeaderData.length.toDouble();// 第一个联系人组的顶部偏移量为顶部系统入口的总高度

    var index = 0;
    
    _contacts.forEach((element) {
      if (element.indexLetter != null && _indexs.contains(element.indexLetter) == false) {
        _indexs.add(element.indexLetter!);

        _indexOffsets.addAll({ index : offsetY });

        index += 1;
        // 基础Cell高度，加上顶部分组头部高度
        offsetY += ContactCell.baseCellHeight + ContactCell.indexHeight;
      }
      else {
        // 基础Cell高度
        offsetY += ContactCell.baseCellHeight;
      }
    });
  }

  ...
}
```

#### IndexBar的事件处理

```
IndexBar(
    dataSource: _indexs,
    callBack: (index, title) {
        if (_indexOffsets[index] != null) {
        _scroller.jumpTo(_indexOffsets[index]!);
        }
    },
)
```

### 项目完整代码

[GitHub](https://github.com/RyukieSama/FlutterStudy/tree/master/fake_wechat)
