Repository: flutter
Issue: There is no way to scroll to an item programmatically in Nested Scroll View with TabBarView body that has CustomScrollViews 
Labels: framework,f: scrolling,has reproducible steps,P3,team-framework,triaged-framework,found in release: 3.24,found in release: 3.26

## Steps to reproduce

As the title suggests, there's currently no way in Flutter to programmatically scroll to a specific item inside a TabView when using it in combination with a NestedScrollView.

There is some related discussion in issue #40740 .


### Expected results

It should be possible to scroll to a specific item in a list programmatically when using CustomScrollView within a NestedScrollView.

### Actual results

When using NestedSliverHeader and assigning a ScrollController, the scrolling functionality breaks. If this is intended behavior, is there an alternative solution to achieve programmatically scrolling to an item?

### Code sample

<details open><summary>Code sample</summary>

```dart
final scrollController = AutoScrollController();

NestedScrollView(
  headerSliverBuilder: (context, innerBoxIsScrolled) => [
    const AppBarWidget(),
    SliverToBoxAdapter(
      child: TabBar(
        indicatorWeight: 0.5,
        indicatorSize: TabBarIndicatorSize.label,
        controller: tabController,
        tabAlignment: TabAlignment.start,
        isScrollable: true,
        tabs: const [
          Tab(height: 32, text: 'Item 1'),
          Tab(height: 32, text: 'Item 2'),
          Tab(height: 32, text: 'Item 3'),
          Tab(height: 32, text: 'Item 4'),
        ],
      ),
    ),
  ],
  body: TabBarView(
    controller: controller,
    children: [
      CustomScrollView(
        controller: scrollController, // Controller to scroll to item
        key: const PageStorageKey('main_group'),
        slivers: [
          // Some widgets
          SliverListWidgetWithController(controller: scrollController, items: items),
        ],
      ),
      CustomScrollView(),
      CustomScrollView(),
      CustomScrollView(),
    ],
  ),
);

```
When using a scroll controller, I can scroll to a specific item, but the header doesn't scroll properly.
```dart
class SliverListWidgetWithController extends HookConsumerWidget {
  const SliverListWidgetWithController({
    required this.items,
    required this.controller, // Passing the controller
    super.key,
  });

  final List<ITEM> items;
  final ScrollController controller;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final _scrollToItem = useCallback(
      (int index) {
        controller.scrollToIndex(
          index,
          duration: const Duration(milliseconds: 500),
        );
      },
    );

    return SliverMainAxisGroup(
      slivers: [
        const SliverGap(8),
        SliverToBoxAdapter(
          child: EasyInfiniteDateTimeLine(
            showTimelineHeader: false,
            firstDate: item.startDate,
            lastDate: item.endDate,
            onDateChange: (date) {
              final index = logicToFindIndex();
              _scrollToItem(index);
            },
          ),
        ),
        for (var i = 0; i < items.length; i++)
          HookBuilder(
            builder: (context) {
              final isExpanded = useValueNotifier(true);

              useEffect(() {
                WidgetsBinding.instance.addPostFrameCallback((_) async {
                  if (someInitialLogic) {
                    _scrollToItem(i);
                  }
                });
                return null;
              }, []);

              return SliverMainAxisGroup(
                slivers: [
                  SliverToBoxAdapter(
                    child: AutoScrollTag(
                      key: ValueKey(i),
                      controller: controller,
                      index: i,
                      child: MyCard(
                        key: _itemKeys.value[i],
                        padding: const EdgeInsets.symmetric(horizontal: 16),
                        margin: EdgeInsets.zero,
                        showBorder: false,
                        borderRadius: 0,
                        child: ItemHeaderWidget(
                          isExpanded: isExpanded,
                          itemId: item[i].id,
                          expanded: (p0) {
                            isExpanded.value = p0;
                          },
                        ),
                      ),
                    ),
                  ),
                  HookBuilder(
                    builder: (context) {
                      useListenable(isExpanded);
                      return SubListWidget(
                        isExpanded: isExpanded,
                        item: item[i],
                        lastOne: i == items.length - 1,
                      );
                    },
                  ),
                ],
              );
            },
          ),
      ],
    );
  }
}

```

The problem is that this approach breaks the headerSliver scrolling behavior due to the ScrollController.

Tried Solutions
Hacky Solution: Passing PrimaryScrollController.of(context) instead of using a custom ScrollController:

```dart
...
 CustomScrollView(
  key: const PageStorageKey('main_group'),
  slivers: [
    // Some widgets
    SliverListWidget(controller: PrimaryScrollController.of(context), items: items),
  ],
),
CustomScrollView(),
CustomScrollView(),
CustomScrollView(),
])))
...
```

```dart
class SliverListWidget extends HookConsumerWidget {
  const SliverListWidget({
    required this.items,
    required this.controller //<<<< ---- passing this as  PrimaryScrollController.of(context) in the parent,
    super.key,
  });

  final List<ItemsObj> items;
  final ScrollController controller;

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    final _itemKeys = useRef(
      List.generate(items.length, (idx) => GlobalKey(debugLabel: 'item $idx')),
    );

 final _scrollToItem = useCallback(
      (int index) {
        if (index < 0 || index >= _itemKeys.value.length) return;

        // Find the position of the item
        final RenderBox renderBox = _itemKeys.value[index].currentContext!
            .findRenderObject() as RenderBox;

        final position = renderBox.localToGlobal(
          Offset.zero,
        );

        // Scroll to the item's position in the list
        controller.animateTo(
          controller.offset + position.dy,
          duration: const Duration(milliseconds: 500),
          curve: Curves.easeInOut,
        );
      },
      [_itemKeys.value],
    );


return SliverMainAxisGroup(
        slivers: [
          const SliverGap(8),
SliverToBoxAdapter(
            child: EasyInfiniteDateTimeLine(
              showTimelineHeader: false,
              firstDate: item.startDate,
              lastDate: item.endDate,
           
              ),
              onDateChange: (date) {
                final index = logicToFindIndex();
// need to scroll to item in outer scroll here but should also scroll the SliverHeader
                _scrollToItem(index);
              },
            ),
          ),
 for (var i = 0; i < items.length; i++)
            HookBuilder(
              builder: (context) {
  final isExpanded = useValueNotifier(true);
            
                useEffect(
                  () {
                    WidgetsBinding.instance.addPostFrameCallback((_) async {
                      if (someInitialLogic) {
                        _scrollToItem(i);
                      }
                    });
                    return null;
                  },
                  [tripId],
                );

                return SliverMainAxisGroup(
                  slivers: [
                    SliverToBoxAdapter(
                      child: MyCard(
                        key: _itemKeys.value[i],
                        padding: const EdgeInsets.symmetric(horizontal: 16),
                        margin: EdgeInsets.zero,
                        showBorder: false,
                        borderRadius: 0,
                        child: ItemHeader(
                          isExpanded: isExpanded,

                          itemId: item[I].id
                          date: date,
                          expanded: (p0) {
                            isExpanded.value = p0;
                          },
                        ),
                      ),
                    ),
                    HookBuilder(
                      builder: (context) {
                        useListenable(isExpanded);
                        return SubListWidget(
                          isExpanded: isExpanded,
                          item: item[I],
                          lastOne: i == item.length - 1,
                        );
                      },
                    ),
                  ],
                );
              },
            ),....
```

GlobalKey Approach: This method works initially on widget build but fails to retrieve the correct widget offset after that. I've also attempted to keep the state alive, but none of the approaches worked.

Manual Sync of Controllers: Attempted to sync PrimaryScrollController.of(context) with the custom controller, which I did not prefer due to non native animation feel and generally feels like the scroll is always slightly off.

```dart
  useEffect(
              () {
                void syncScrollControllers() {
                  // Get the current scroll positions of both controllers
                  final autoTagOffset = autoTagController.position.pixels;
                  final scrollMaxExtent =
                      scrollController.position.maxScrollExtent;
                  final scrollOffset = scrollController.position.pixels;

                  // Define the threshold (250 pixels)
                  const double scrollThreshold = 250.0;

                  // If autoTagController scrolls within the first 250 pixels
                  if (autoTagOffset <= scrollThreshold) {
                    // Calculate the scroll proportion based on 250px limit
                    final mappedScrollOffset =
                        scrollMaxExtent * (autoTagOffset / scrollThreshold);

                    // Scroll the scrollController to the mapped offset
                    scrollController.jumpTo(mappedScrollOffset);
                  } else if (scrollOffset != scrollMaxExtent) {
                    // If autoTagController scrolls past 250px, keep scrollController at max
                    scrollController.jumpTo(scrollMaxExtent);
                  }
                }

                WidgetsBinding.instance.addPostFrameCallback((_) {
                  autoTagController.addListener(syncScrollControllers);
                });

                return () {
                  autoTagController.removeListener(syncScrollControllers);
                };
              },
              [],
            );
```

</details>


### Screenshots or Video

<details open>
<summary>Screenshots / Video demonstration</summary>

![nestedscrollview](https://github.com/user-attachments/assets/d80ea507-226f-48bd-83b6-c3d064e89f5e)




</details>