'react native flatlist animation is not working as expected on RTL devices

I'm working in app that needs flatlist animation with RTL devices. I built a small app to understand how it will work. When using the app with LTR devices, it works just fine, and when I use I18nManager.forceRTL(true) everything messed up.

I have three slides with two components, the first component shows the slide itself and the second shows the number of the slide.

When apply force RTL the first component starts with the first slide but the second component start form the last one! enter image description here

Also, when I use two animations (transform and opacity) for one element of the first component. The first slide and the last slide disappeared! enter image description here

Here is my code with force RTL:

import React, { useRef } from "react";
import {
  SafeAreaView,
  View,
  StyleSheet,
  Text,
  StatusBar,
  Dimensions,
  Animated,
} from "react-native";
import { I18nManager } from "react-native";
I18nManager.allowRTL(true);
I18nManager.forceRTL(true);

const { width, height } = Dimensions.get("window");

const DATA = [
  {
    id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
    title: "First Slide",
    number: "1",
  },
  {
    id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
    title: "Second Slide",
    number: "2",
  },
  {
    id: "58694a0f-3da1-471f-bd96-145571e29d72",
    title: "Third Slide",
    number: "3",
  },
];

const Item = ({ title, index, scrollX }) => {
  const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
  const opacityInputRange = [
    (index - 0.4) * width,
    index * width,
    (index + 0.4) * width,
  ];
  const scale = scrollX.interpolate({
    inputRange,
    outputRange: [0, 1, 0],
  });

  const opacity = scrollX.interpolate({
    inputRange: opacityInputRange,
    outputRange: [0, 1, 0],
  });

  return (
    <View style={styles.mainConatainer}>
      <Animated.View
        style={{
          backgroundColor: "blue",
          width: width / 1.6,
          height: height / 3,
          alignItems: "center",
          justifyContent: "center",
          borderRadius: width,
          transform: [{ scale }],
          opacity,
        }}
      >
        <Animated.Text
          style={{
            fontSize: 32,
            color: "white",
            transform: [{ scale }],
          }}
        >
          {title}
        </Animated.Text>
      </Animated.View>
    </View>
  );
};

const Footer = ({ scrollX }) => {
  return (
    <View>
      {DATA.map(({ number }, index) => {
        const inputRange = [
          (index - 1) * width,
          index * width,
          (index + 1) * width,
        ];
        const scale = scrollX.interpolate({
          inputRange,
          outputRange: [0, 1, 0],
        });
        return (
          <Animated.View key={index} style={{ transform: [{ scale }] }}>
            <Text
              style={{
                fontSize: 50,
                alignItems: "center",
                marginBottom: 50,
                position: "absolute",
                bottom: "40%",
                right: "50%",
                color: "blue",
              }}
            >
              {number}
            </Text>
          </Animated.View>
        );
      })}
    </View>
  );
};
export default function App() {
  const scrollX = useRef(new Animated.Value(0)).current;
  return (
    <SafeAreaView style={styles.container}>
      <Animated.FlatList
        data={DATA}
        renderItem={({ item, index }) => (
          <Item {...item} index={index} scrollX={scrollX} />
        )}
        keyExtractor={(item) => item.id}
        horizontal
        pagingEnabled
        showsHorizontalScrollIndicator={false}
        onScroll={Animated.event(
          [{ nativeEvent: { contentOffset: { x: scrollX } } }],
          { useNativeDriver: true }
        )}
        scrollEventThrottle={16}
        inverted={true}
      />
      <Footer scrollX={scrollX} />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
    backgroundColor: "lightgrey",
  },
  mainConatainer: {
    width,
    alignItems: "center",
    justifyContent: "center",
  },
  item: {
    backgroundColor: "blue",
    width: width / 1.6,
    height: height / 3,
    alignItems: "center",
    justifyContent: "center",
    borderRadius: width,
  },
  title: {
    fontSize: 64,
    color: "white",
  },
});

Without force RTL:

import React, { useRef } from "react";
import {
  SafeAreaView,
  View,
  StyleSheet,
  Text,
  StatusBar,
  Dimensions,
  Animated,
} from "react-native";

const { width, height } = Dimensions.get("window");

const DATA = [
  {
    id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
    title: "First Slide",
    number: "1",
  },
  {
    id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
    title: "Second Slide",
    number: "2",
  },
  {
    id: "58694a0f-3da1-471f-bd96-145571e29d72",
    title: "Third Slide",
    number: "3",
  },
];

const Item = ({ title, index, scrollX }) => {
  const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
  const opacityInputRange = [
    (index - 0.4) * width,
    index * width,
    (index + 0.4) * width,
  ];
  const scale = scrollX.interpolate({
    inputRange,
    outputRange: [0, 1, 0],
  });

  const opacity = scrollX.interpolate({
    inputRange: opacityInputRange,
    outputRange: [0, 1, 0],
  });

  return (
    <View style={styles.mainConatainer}>
      <Animated.View
        style={{
          backgroundColor: "blue",
          width: width / 1.6,
          height: height / 3,
          alignItems: "center",
          justifyContent: "center",
          borderRadius: width,
          transform: [{ scale }],
          opacity,
        }}
      >
        <Animated.Text
          style={{
            fontSize: 32,
            color: "white",
            transform: [{ scale }],
          }}
        >
          {title}
        </Animated.Text>
      </Animated.View>
    </View>
  );
};

const Footer = ({ scrollX }) => {
  return (
    <View>
      {DATA.map(({ number }, index) => {
        const inputRange = [
          (index - 1) * width,
          index * width,
          (index + 1) * width,
        ];
        const scale = scrollX.interpolate({
          inputRange,
          outputRange: [0, 1, 0],
        });
        return (
          <Animated.View key={index} style={{ transform: [{ scale }] }}>
            <Text
              style={{
                fontSize: 50,
                alignItems: "center",
                marginBottom: 50,
                position: "absolute",
                bottom: "40%",
                right: "50%",
                color: "blue",
              }}
            >
              {number}
            </Text>
          </Animated.View>
        );
      })}
    </View>
  );
};
export default function App() {
  const scrollX = useRef(new Animated.Value(0)).current;
  return (
    <SafeAreaView style={styles.container}>
      <Animated.FlatList
        data={DATA}
        renderItem={({ item, index }) => (
          <Item {...item} index={index} scrollX={scrollX} />
        )}
        keyExtractor={(item) => item.id}
        horizontal
        pagingEnabled
        showsHorizontalScrollIndicator={false}
        onScroll={Animated.event(
          [{ nativeEvent: { contentOffset: { x: scrollX } } }],
          { useNativeDriver: true }
        )}
        scrollEventThrottle={16}
        inverted={true}
      />
      <Footer scrollX={scrollX} />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
    backgroundColor: "lightgrey",
  },
  mainConatainer: {
    width,
    alignItems: "center",
    justifyContent: "center",
  },
  item: {
    backgroundColor: "blue",
    width: width / 1.6,
    height: height / 3,
    alignItems: "center",
    justifyContent: "center",
    borderRadius: width,
  },
  title: {
    fontSize: 64,
    color: "white",
  },
});


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source