Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу о давней проблеме — ограничении метода Scrollable.ensureVisible, которое не работает для элементов, находящихся за пределами видимости в ListView.
Суть проблемы
Метод Scrollable.ensureVisible должен прокручивать список до нужного виджета, но работает он только для элементов, которые уже отрендерены на экране. Когда элемент находится далеко за пределами видимости, Flutter просто не создает для него контекст, и метод перестает работать.
Как проявляется
Допустим, у вас ListView с 11 элементами. При попытке прокрутить к элементу с индексом 1-7 все работает отлично. Но как только пытаетесь прокрутить к 8-му, 9-му или дальше — ничего не происходит.
// Не работает для элементов вне экрана
WidgetsBinding.instance.addPostFrameCallback((_) {
Scrollable.ensureVisible(selectedDataKey.currentContext);
// currentContext будет null для невидимых элементов!
});
Почему до сих пор не исправлено
Команда Flutter признала эту проблему еще в 2018 году. Причина — техническая сложность реализации. Чтобы прокрутить к элементу, Flutter должен знать его позицию и размер. Но для невидимых элементов эта информация недоступна, так как они еще не созданы.
Flutter не знает:
▫️в какую сторону прокручивать список
▫️какой offset использовать
▫️какой высоты будет целевой элемент
Попытка перебрать все элементы для поиска нужного была бы слишком дорогой операцией.
Workaround решения
Вариант 1: SingleChildScrollView для статичных списков
Если список небольшой и не меняется, замените ListView на SingleChildScrollView с Column:
SingleChildScrollView(
child: Column(
children: [
Container(key: firstKey, child: Text('Item 1')),
Container(key: secondKey, child: Text('Item 2')),
// Все элементы рендерятся сразу
],
),
)
// Теперь ensureVisible работает
Scrollable.ensureVisible(firstKey.currentContext);
Минус: не подходит для длинных списков — все элементы рендерятся сразу, что убивает производительность.
Вариант 2: ScrollablePositionedList
Самое популярное решение — использовать пакет scrollable_positioned_list, который добавляет нужную функциональность:
ItemScrollController _scrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemScrollController: _scrollController,
itemCount: items.length,
itemBuilder: (context, index) => items[index],
)
// Прокрутка к любому индексу
_scrollController.scrollTo(
index: 150,
duration: Duration(seconds: 1),
curve: Curves.ease,
);
Это решение работает даже для элементов далеко за пределами экрана.
Вариант 3: Ручная прокрутка жестами
Хелпер для тестов, который эмулирует прокрутку до появления элемента:
Future<void> ensureVisibleByScrolling(
Finder finder, {
required Offset scrollFrom,
Offset scrollBy = const Offset(0, -50),
int maxScrolls = 100,
}) async {
final gesture = await widgetTester.startGesture(scrollFrom);
for (var i = 0; i < maxScrolls; i++) {
await gesture.moveBy(scrollBy);
await pump();
if (widgetTester.widgetList(finder).length == 1) {
break;
}
}
await gesture.cancel();
await widgetTester.ensureVisible(finder);
}
Этот метод постепенно прокручивает список, пока элемент не появится на экране.
Статус в 2025 году
К сожалению, официального решения в стандартной библиотеке Flutter до сих пор нет. Issue открыт с 2018 года, и команда Flutter признала, что не планирует в ближайшее время добавлять нативный scrollToIndex для ListView.
А какой вариант решения проблемы используете вы?
