滚动到索引适用于 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),
);
}
您可以尝试使用 Scrollable.ensureVisible (您必须为每个列表项提供一个唯一的键,然后使用它来确保该项目可见)。
您必须考虑您正在滚动的内容,您正在滚动“CustomScrollView”,因此您需要将相同的控制器从 AutoScrollTag 分配给您的 CustomScrollView,然后scrollToIndex 将按预期工作。
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
controller: controller, /// Change this line
这是我们的定制解决方案:
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),
);
}
}