如何用SliverList滚动到索引?

问题描述 投票:0回答:3

滚动到索引适用于 ListView 小部件,但不适用于 customScrollView 中的 SliverList。修改了this“滚动到索引”示例,将 ListView 替换为 SliverList。此外,尝试过过去类似的问题,但没有任何运气。

期望的结果是通过点击按钮滚动到索引。

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.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: MyHomePage(title: 'Scroll To Index Demo'),
    );
  }
}
 
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
 
  final String title;
 
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
 
class _MyHomePageState extends State<MyHomePage> {
  static const maxCount = 100;
  final random = math.Random();
  final scrollDirection = Axis.vertical;
 
  late AutoScrollController controller;
  late List<List<int>> randomList;
 
  @override
  void initState() {
    super.initState();
    controller = AutoScrollController(
        viewportBoundaryGetter: () =>
            Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
        axis: scrollDirection);
    randomList = List.generate(
        maxCount, (index) => <int>[index, (200 * random.nextDouble()).toInt()]);
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        controller: ScrollController(),
        slivers: [
          SliverAppBar(
            pinned: true,
            title: Text("App bar"),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return Padding(
                  padding: EdgeInsets.all(8),
                  child: _getRow(
                    randomList[index][0],
                    math.max(randomList[index][1].toDouble(), 50.0),
                  ),
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _scrollToIndex,
        tooltip: 'Increment',
        child: Text(counter.toString()),
      ),
    );
  }
 
  int counter = -1;
  Future _scrollToIndex() async {
    setState(() {
      counter++;
 
      if (counter >= maxCount) counter = 0;
    });
 
    await controller.scrollToIndex(counter,
        preferPosition: AutoScrollPosition.begin);
    controller.highlight(counter);
  }
 
  Widget _getRow(int index, double height) {
    return _wrapScrollTag(
      index: index,
      child: Container(
        padding: EdgeInsets.all(8),
        alignment: Alignment.topCenter,
        height: height,
        decoration: BoxDecoration(
            border: Border.all(color: Colors.lightBlue, width: 4),
            borderRadius: BorderRadius.circular(12)),
        child: Text('index: $index, height: $height'),
      ),
    );
  }
 
  Widget _wrapScrollTag({required int index, required Widget child}) =>
      AutoScrollTag(
        key: ValueKey(index),
        controller: controller,
        index: index,
        child: child,
        highlightColor: Colors.black.withOpacity(0.7),
      );
}
flutter flutter-layout
3个回答
1
投票

您可以尝试使用 Scrollable.ensureVisible (您必须为每个列表项提供一个唯一的键,然后使用它来确保该项目可见)。


0
投票

您必须考虑您正在滚动的内容,您正在滚动“CustomScrollView”,因此您需要将相同的控制器从 AutoScrollTag 分配给您的 CustomScrollView,然后scrollToIndex 将按预期工作。

@override
Widget build(BuildContext context) {
return Scaffold(
  body: CustomScrollView(
    controller: controller,  /// Change this line

0
投票

这是我们的定制解决方案:

enum GroupedListOrder { acs, desc }

class SliverGroupedListView<T, E> extends StatefulWidget {
  /// Items of which [itemBuilder] or [indexedItemBuilder] produce the list.
  final List<T> elements;

  /// Defines which elements are grouped together.
  ///
  /// Function is called for each element in the list, when equal for two
  /// elements, those two belong to the same group.
  final E Function(T element) groupBy;

  /// Can be used to define a custom sorting for the groups.
  ///
  /// If not set groups will be sorted with their natural sorting order or their
  /// specific [Comparable] implementation.
  final int Function(E value1, E value2)? groupComparator;

  /// Can be used to define a custom sorting for the elements inside each group.
  ///
  /// If not set elements will be sorted with their natural sorting order or
  /// their specific [Comparable] implementation.
  final int Function(T element1, T element2)? itemComparator;

  /// Called to build group separators for each group.
  /// Value is always the groupBy result from the first element of the group.
  ///
  /// Will be ignored if [groupHeaderBuilder] is used.
  final Widget Function(E value, {bool isFirst})? groupSeparatorBuilder;

  /// Same as [groupSeparatorBuilder], will be called to build group separators
  /// for each group.
  /// The passed element is always the first element of the group.
  ///
  /// If defined [groupSeparatorBuilder] wont be used.
  final Widget Function(T element)? groupHeaderBuilder;

  /// Called to build children for the list with
  /// 0 <= element < elements.length.
  final Widget Function(BuildContext context, T element)? itemBuilder;

  /// Called to build children for the list with
  /// 0 <= element, index < elements.length
  final Widget Function(BuildContext context, T element, int index)?
      indexedItemBuilder;

  /// Whether the order of the list is ascending or descending.
  ///
  /// Defaults to ASC.
  final GroupedListOrder order;

  /// Whether the elements will be sorted or not. If not it must be done
  ///  manually.
  ///
  /// Defauts to true.
  final bool sort;

  /// Called to build separators for between each item in the list.
  final Widget separator;

  /// Creates a [SliverGroupedListView]
  const SliverGroupedListView({
    super.key,
    required this.elements,
    required this.groupBy,
    this.groupComparator,
    this.groupSeparatorBuilder,
    this.groupHeaderBuilder,
    this.itemBuilder,
    this.indexedItemBuilder,
    this.itemComparator,
    this.order = GroupedListOrder.acs,
    this.sort = true,
    this.separator = const SizedBox.shrink(),
  })  : assert(itemBuilder != null || indexedItemBuilder != null),
        assert(groupSeparatorBuilder != null || groupHeaderBuilder != null);

  @override
  State<StatefulWidget> createState() => _SliverGroupedListViewState<T, E>();
}

class _SliverGroupedListViewState<T, E>
    extends State<SliverGroupedListView<T, E>> {
  final LinkedHashMap<String, GlobalKey> _keys = LinkedHashMap();
  List<T> _sortedElements = [];

  @override
  Widget build(BuildContext context) {
    _sortedElements = _sortElements();
    const hiddenIndex = 0;
    isSeparator(int i) => i.isEven;

    return SliverToBoxAdapter(
      child: Column(
        children: _buildItemList(
          context,
          length: _sortedElements.length * 2,
          hiddenIndex: hiddenIndex,
          isSeparator: isSeparator,
        ),
      ),
    );
  }

  List<Widget> _buildItemList(
    BuildContext context, {
    required int length,
    required int hiddenIndex,
    required bool Function(int) isSeparator,
  }) {
    final List<Widget> itemList = [];
    for (int i = 0; i < length; i++) {
      final actualIndex = i ~/ 2;
      if (i == hiddenIndex) {
        itemList.add(
          Opacity(
            opacity: 1,
            child: _buildGroupSeparator(
              _sortedElements[actualIndex],
              isFirst: true,
            ),
          ),
        );
        continue;
      }
      if (isSeparator(i)) {
        final curr = widget.groupBy(_sortedElements[actualIndex]);
        final prev = widget.groupBy(_sortedElements[actualIndex - 1]);
        if (prev != curr) {
          itemList.add(_buildGroupSeparator(_sortedElements[actualIndex]));
          continue;
        }
        itemList.add(widget.separator);
        continue;
      }
      itemList.add(_buildItem(context, actualIndex));
    }
    return itemList;
  }

  Container _buildItem(BuildContext context, int actualIndex) {
    final key = GlobalKey();
    _keys['$actualIndex'] = key;
    return Container(
      key: key,
      child: widget.indexedItemBuilder == null
          ? widget.itemBuilder!(context, _sortedElements[actualIndex])
          : widget.indexedItemBuilder!(
              context,
              _sortedElements[actualIndex],
              actualIndex,
            ),
    );
  }

  List<T> _sortElements() {
    List<T> elements = widget.elements;
    if (widget.sort && elements.isNotEmpty) {
      elements.sort((e1, e2) {
        int? compareResult;
        // compare groups
        if (widget.groupComparator != null) {
          compareResult =
              widget.groupComparator!(widget.groupBy(e1), widget.groupBy(e2));
        } else if (widget.groupBy(e1) is Comparable) {
          compareResult = (widget.groupBy(e1) as Comparable)
              .compareTo(widget.groupBy(e2) as Comparable);
        }
        // compare elements inside group
        if (compareResult == null || compareResult == 0) {
          if (widget.itemComparator != null) {
            compareResult = widget.itemComparator!(e1, e2);
          } else if (e1 is Comparable) {
            compareResult = e1.compareTo(e2);
          }
        }
        return compareResult!;
      });
      if (widget.order == GroupedListOrder.desc) {
        elements = elements.reversed.toList();
      }
    }
    return elements;
  }

  Widget _buildGroupSeparator(T element, {bool isFirst = false}) {
    if (widget.groupHeaderBuilder == null) {
      return widget.groupSeparatorBuilder!(
        widget.groupBy(element),
        isFirst: isFirst,
      );
    }
    return widget.groupHeaderBuilder!(element);
  }
}

  Future<void> _scrollToContext(
    GlobalKey<State<CategorySeparator>>? key,
  ) async {
    if (key == null) return;
    final context = key.currentContext;
    if (context != null) {
      final bool isFirstSeparator = key.currentState?.widget.isFirst ?? false;
      await Scrollable.ensureVisible(
        context,
        alignment: ((isFirstSeparator ? 36 : 0) + kToolbarHeight + 47) /
            (MediaQuery.sizeOf(context).height -
                MediaQuery.of(context).viewPadding.top -
                MediaQuery.of(context).viewPadding.bottom),
        duration: const Duration(milliseconds: 300),
      );
    }
  }
© www.soinside.com 2019 - 2024. All rights reserved.