improve initial item scroll for list/grid

This commit is contained in:
Fabian Freund
2026-02-01 10:50:11 +01:00
parent 6d2f9ff837
commit 7ed6bbf9a9
2 changed files with 66 additions and 38 deletions
@@ -187,37 +187,49 @@ class _TabGridView extends HookConsumerWidget {
[screenWidth, crossAxisCount], [screenWidth, crossAxisCount],
); );
final lastScroll = useRef<String?>(null); final didInitialScroll = useRef(false);
useEffect(() { useEffect(() {
if (didInitialScroll.value) return null;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (scrollController.hasClients) { if (scrollController.hasClients && activeTab != null) {
if (lastScroll.value != activeTab) {
final index = filteredTabEntities.value.indexWhere( final index = filteredTabEntities.value.indexWhere(
(entity) => entity.tabId == activeTab, (entity) => entity.tabId == activeTab,
); );
if (index > -1) { if (index > -1) {
final offset = (index ~/ 2) * itemSize.height; final row = index ~/ crossAxisCount;
final viewportStart = scrollController.offset;
final viewportEnd =
viewportStart + scrollController.position.viewportDimension;
final tabStart = row * itemSize.height;
final tabEnd = tabStart + itemSize.height;
if (offset != scrollController.offset) { final isVisible =
lastScroll.value = activeTab; tabStart >= viewportStart && tabEnd <= viewportEnd;
if (!isVisible) {
unawaited( unawaited(
scrollController.animateTo( scrollController.animateTo(
offset, tabStart,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
), ),
); );
didInitialScroll.value = true;
} else {
didInitialScroll.value = true;
} }
} } else {
didInitialScroll.value = true;
} }
} }
}); });
return null; return null;
}, [filteredTabEntities, activeTab]); }, [activeTab]);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
@@ -175,37 +175,53 @@ class _TabListView extends HookConsumerWidget {
final activeTab = ref.watch(selectedTabProvider); final activeTab = ref.watch(selectedTabProvider);
final lastScroll = useRef<String?>(null); final didInitialScroll = useRef(false);
useEffect(() { useEffect(() {
if (didInitialScroll.value) return null;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (scrollController.hasClients) { if (scrollController.hasClients && activeTab != null) {
if (lastScroll.value != activeTab) {
final index = filteredTabEntities.value.indexWhere( final index = filteredTabEntities.value.indexWhere(
(entity) => entity.tabId == activeTab, (entity) => entity.tabId == activeTab,
); );
if (index > -1) { if (index > -1) {
final offset = index * _itemHeight; final viewportStart = scrollController.offset;
final viewportEnd =
viewportStart + scrollController.position.viewportDimension;
final tabStart = index * _itemHeight;
final tabEnd = tabStart + _itemHeight;
if (offset != scrollController.offset) { final isVisible =
lastScroll.value = activeTab; tabStart >= viewportStart && tabEnd <= viewportEnd;
if (!isVisible) {
final targetOffset = (tabStart - _itemHeight).clamp(
0.0,
scrollController.position.maxScrollExtent,
);
unawaited( unawaited(
scrollController.animateTo( scrollController.animateTo(
offset, targetOffset,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut, curve: Curves.easeInOut,
), ),
); );
didInitialScroll.value = true;
} else {
didInitialScroll.value = true;
} }
} } else {
didInitialScroll.value = true;
} }
} }
}); });
return null; return null;
}, [filteredTabEntities, activeTab]); }, [activeTab]);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),