Indicator Glitch in React native scrollspy

I am experiencing an issue with the tab indicator on Android when tabs autoscroll. The indicator disappears during the autoscroll process, and when I manually scroll the tabs back to their initial position, the indicator reappears at that position.

This issue does not occur on iOS, where the code functions as expected. I have attempted several solutions, such as removing the indicator from the ListHeaderComponent and displaying it externally, but I have not been able to resolve the problem.

I have included both the code and a GIF demonstrating the behavior. If anyone has encountered a similar issue or has any suggestions for resolving this, I would greatly appreciate your insights.

Thank you in advance for your help!

import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { faker } from "@faker-js/faker";
import React, { useEffect, useRef, useState, useCallback } from "react";
import {
  FlatList,
  Image,
  LayoutRectangle,
  SectionList,
  StyleSheet,
  TouchableOpacity,
  useWindowDimensions,
  ViewToken,
} from "react-native";
import Animated, {
  AnimatedProps,
  interpolate,
  runOnJS,
  SharedValue,
  useAnimatedScrollHandler,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from "react-native-reanimated";
import getItemLayout from "react-native-get-item-layout-section-list";
import { useSafeAreaInsets } from "react-native-safe-area-context";

faker.seed(123);
const AnimatedSectionList = Animated.createAnimatedComponent(SectionList);
const getRandomData = () =>
  [...Array(faker.number.int({ min: 2, max: 5 }))].map(() => ({
    key: faker.string.uuid(),
    song: faker.music.songName(),
    artist: faker.music.artist(),
    album: faker.music.album(),
    cover: faker.image.personPortrait(),
  }));

const _sections = [...Array(5).keys()].map((index) => ({
  key: faker.string.uuid(),
  title: faker.music.genre(),
  data: getRandomData(),
  sectionIndex: index,
}));

const _spacing = 12;
const _indicatorSize = 4;
const ITEM_HEIGHT = 99;
const SECTION_HEADER_HEIGHT = 46;

const buildGetItemLayout = getItemLayout({
  getItemHeight: ITEM_HEIGHT,
  getSectionHeaderHeight: SECTION_HEADER_HEIGHT,
});

type TabsLayout = Record<number, LayoutRectangle>;

// Memoized list item component
const DummyItem = React.memo(
  ({ item }: { item:any}) => (
    <ThemedView style={styles.itemContainer}>
      <ThemedView style={{ gap: _spacing, flexDirection: "row" }}>
        <Image
          source={{ uri: item.cover }}
          style={{
            height: 50,
            aspectRatio: 1,
            borderRadius: _spacing / 2,
            borderWidth: 2,
            borderColor: "rgba(255,255,255,0.5)",
          }}
        />
        <ThemedView style={{ flex: 1, justifyContent: "space-between" }}>
          <ThemedText style={styles.artistText}>{item?.artist}</ThemedText>
          <ThemedText style={styles.songText}>{item?.song}</ThemedText>
          <ThemedText style={styles.albumText}>{item?.album}</ThemedText>
        </ThemedView>
      </ThemedView>
    </ThemedView>
  )
);

function Indicator({ measurements }: { measurements: LayoutRectangle }) {

  const _stylez = useAnimatedStyle(() => ({
    width: withTiming(measurements.width, { duration: 150 }),
    left: withTiming(measurements.x, { duration: 150 }),
    top: measurements.height,
  }));

  return <Animated.View style={[styles.indicator, _stylez]} />;
}

const Tabs = React.memo(
  ({
    activeSectionIndex,
    onTabPress,
  }: {
    activeSectionIndex: number;
    onTabPress: (index: number) => void;
  }) => {
    const _tabsLayout = useRef<TabsLayout>({});
    
    const ref = useRef<FlatList>(null);
    const [isDoneMeasuring, setIsDoneMeasuring] = useState(false);

    const scrollToIndex = useCallback((index: number) => {
      ref.current?.scrollToIndex({
        index,
        animated: true,
        viewPosition: 0.5,
      });
    }, []);

    useEffect(() => {
      scrollToIndex(activeSectionIndex);
    }, [activeSectionIndex, scrollToIndex]);

    const renderTabItem = useCallback(
      ({ item, index }: { item: (typeof _sections)[0]; index: number }) => (
        <TouchableOpacity onPress={() => onTabPress(index)}>
          <ThemedView style={styles.tabItem}>
            <ThemedText style={styles.tabText}>{item?.title}</ThemedText>
          </ThemedView>
        </TouchableOpacity>
      ),
      [onTabPress]
    );

    return (
      <FlatList
        ref={ref}
        data={_sections}
        initialNumToRender={5}
        maxToRenderPerBatch={5}
        windowSize={21}
        getItemLayout={(_, index) => ({
          length: _tabsLayout.current[index]?.width || 0,
          offset: _tabsLayout.current[index]?.x || 0,
          index,
        })}
        CellRendererComponent={({ children, index, ...rest }) => (
          <ThemedView
            {...rest}
            onLayout={(e) => {
              _tabsLayout.current[index] = e.nativeEvent.layout;
              if (
                Object.keys(_tabsLayout.current).length === _sections.length &&
                !isDoneMeasuring
              ) {
                setIsDoneMeasuring(true);
              }              
            }}
          >
            {children}
          </ThemedView>
        )}
        renderItem={renderTabItem}
        horizontal
        style={styles.tabsContainer}
        showsHorizontalScrollIndicator={false}
        scrollEventThrottle={16}
        contentContainerStyle={styles.tabsContent}
        ListHeaderComponent={
          isDoneMeasuring ? (
            <Indicator measurements={_tabsLayout.current[activeSectionIndex]} />
          ) : null
        }
        ListHeaderComponentStyle={styles.indicatorPosition}
      />
    );
  }
);

const _headerHeight = 350;
const _headerTabsHeight = 49;
const _headerTopNav = 80;
const _headerImageHeight = _headerHeight - _headerTabsHeight;
const _topThreshold = _headerHeight - _headerTopNav;

const ScrollHeader = React.memo(
  ({
    selectedSection,
    onTabPress,
    scrollY,
  }: {
    selectedSection: number;
    onTabPress: (index: number) => void;
    scrollY: SharedValue<number>;
  }) => {
    const { top } = useSafeAreaInsets();

    const headerStylez = useAnimatedStyle(() => ({
      transform: [
        {
          translateY: interpolate(
            scrollY.value,
            [-1, 0, 1, _topThreshold - 1, _topThreshold],
            [1, 0, -1, -_topThreshold, -_topThreshold]
          ),
        },
      ],
    }));

    const imageStyles = useAnimatedStyle(() => ({
      opacity: interpolate(
        scrollY.value,
        [-1, 0, _headerImageHeight - 1, _headerImageHeight],
        [1, 1, 0.3, 0]
      ),
      transform: [
        {
          scale: interpolate(
            scrollY.value,
            [-1, 0, 1],
            [1 + 1 / _headerHeight, 1, 1]
          ),
        },
      ],
    }));

    return (
      <Animated.View style={[styles.headerContainer, headerStylez]}>
        <Animated.Image
          source={{
            uri: "https://images.pexels.com/photos/7497788/pexels-photo-7497788.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
          }}
          style={[styles.headerImage, imageStyles]}
          resizeMode="cover"
        />
        <Tabs activeSectionIndex={selectedSection} onTabPress={onTabPress} />
      </Animated.View>
    );
  }
);

const Home = () => {
  const [selectedSection, setSelectedSection] = useState(0);
  const activeSectionIndexRef = useRef(0);
  const scrollingFromTabPress = useRef(false);
  const sectionRef = useRef<SectionList>(null);
  const { height } = useWindowDimensions();
  const scrollY = useSharedValue(0);

  const setScrollingFromTabPress = useCallback((value: boolean) => {
    scrollingFromTabPress.current = value;
  }, []);

  const onScroll = useAnimatedScrollHandler({
    onScroll: (e) => {
      scrollY.value = e.contentOffset.y;
    },
    onBeginDrag: () => {
      runOnJS(setScrollingFromTabPress)(false);
    },
  });

  const scrollToSection = useCallback((index: number) => {
    sectionRef.current?.scrollToLocation({
      itemIndex: 0,
      sectionIndex: index,
      animated: true,
      viewOffset: 0,
      viewPosition: 0,
    });
  }, []);

  const handleViewableItemsChanged = useCallback(
    ({ viewableItems }: { viewableItems: ViewToken[] }) => {
      if (scrollingFromTabPress.current) return;

      const section = viewableItems[0]?.section;
      if (!section) return;

      const { sectionIndex } = section as (typeof _sections)[0];
      if (sectionIndex !== selectedSection) {
        activeSectionIndexRef.current = sectionIndex;
        setSelectedSection(sectionIndex);
      }
    },
    [selectedSection]
  );

  const viewabilityConfig = useRef({
    minimumViewTime: 50,
    itemVisiblePercentThreshold: 50,
    waitForInteraction: true,
  }).current;

  return (
    <ThemedView style={styles.container}>
      <AnimatedSectionList
        ref={sectionRef as any}
        sections={_sections}
        keyExtractor={(item:any) => item.key}
        renderItem={({ item }) => <DummyItem item={item} />}
        contentContainerStyle={[
          styles.sectionListStyle,
          { paddingBottom: height / 3, paddingTop: _headerHeight },
        ]}
        renderSectionHeader={({ section: { title } }:{section:any}) => (
          <ThemedView style={styles.sectionHeader}>
            <ThemedText style={styles.sectionHeaderText}>{title}</ThemedText>
          </ThemedView>
        )}
        getItemLayout={buildGetItemLayout}
        onViewableItemsChanged={handleViewableItemsChanged}
        viewabilityConfig={viewabilityConfig}
        onScroll={onScroll}
        scrollEventThrottle={16}
        initialNumToRender={10}
        maxToRenderPerBatch={10}
        windowSize={21}
        bounces={false}
        removeClippedSubviews={true}
        stickyHeaderHiddenOnScroll
      />
      <ScrollHeader
        scrollY={scrollY}
        onTabPress={(index) => {
          if (selectedSection !== index) {
            setSelectedSection(index);
            scrollToSection(index);
            scrollingFromTabPress.current = true;
          }
        }}
        selectedSection={selectedSection}
      />
    </ThemedView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  itemContainer: {
    padding: _spacing,
    borderBottomWidth: 1,
    borderBottomColor: "rgba(0,0,0,0.05)",
  },
  songText: {
    fontSize: 16,
    fontWeight: "bold",
  },
  albumText: {
    fontSize: 10,
    opacity: 0.3,
  },
  sectionListStyle: {},
  artistText: {
    fontSize: 14,
    opacity: 0.3,
  },
  sectionHeader: {
    backgroundColor: "#fff",
    paddingVertical: _spacing * 2,
    paddingHorizontal: _spacing,
  },
  sectionHeaderText: {
    fontSize: 18,
    fontWeight: "700",
  },
  indicator: {
    height: _indicatorSize,
    backgroundColor: "#000",
  },
  tabsContainer: {
    flexGrow: 0,
    paddingBottom: _indicatorSize,
    height: _headerTabsHeight,
  },
  tabsContent: {
    gap: _spacing * 2,
    paddingHorizontal: _spacing,
  },
  indicatorPosition: {
    position: "absolute",
  },
  tabItem: {
    paddingVertical: _spacing,
  },
  tabText: {
    fontWeight: "600",
  },
  headerContainer: {
    height: _headerHeight,
    position: "absolute",
    backgroundColor: "#fff",
    top: 0,
    left: 0,
    right: 0,
  },
  headerImage: {
    height: _headerImageHeight,
    width: "100%",
  },
});

export default Home;

enter image description here