Gracie williams
Gracie williams

Reputation: 1145

Get position of individual Items in flatlist

I have flatlist horizontal like below:

const DATA = [
  {
    id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
    title: 'First Item',
  },
  {
    id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
    title: 'Second Item',
  },
  {
    id: '58694a0f-3da1-471f-bd96-145571e29d72',
    title: 'Third Item',
  },
    {
    id: 'bd7acbea-c1b1-46c2-aed5-3ad353abb28ba',
    title: 'Fourth Item',
  },
  {
    id: '3ac68afc-c605-48d3-a4f8-fbd291aa97f63',
    title: 'Fifth Item',
  },
  {
    id: '58694a0f-3da1-471f-bd961-145571e29d72',
    title: 'Sixth Item',
  },
];

const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const App = () => {
  const renderItem = ({ item }) => (
    <Item title={item.title} />
  );

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        horizontal
        data={DATA}
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
}

Whenever Item entered the viewport, I want to add animation to that element. I can get X and Y position of scroll with onScroll, now how do I get the positions of items to check if its in view port or if it went away from viewport...

Upvotes: 1

Views: 4653

Answers (2)

PhantomSpooks
PhantomSpooks

Reputation: 3540

I implemented the basic fade in/out animation example into the Item component. Whether it fades out or in is decided by the prop isViewable

// Item.js
const Item = (props) => { 
  const {
    item:{title, isViewable}
  } = props
  /*
  I copied and pasted the basic animation example from the react-native dev page
  */
  const fadeAnim = useRef(new Animated.Value(1)).current;
  const fadeIn = () => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver:false
    }).start();
  };
  const fadeOut = () => {
    Animated.timing(fadeAnim, {
      toValue: 0,
      duration: 1500,
      useNativeDriver:false
    }).start();
  };
  /*  end of animation example*/
  
  // fade in/out base on if isViewable
  if(isViewable || isViewable == 0)
    fadeIn()
  else
    fadeOut()
  const animation = {opacity:fadeAnim}
  return (
    //add animation to Animated.View
    <Animated.View style={[style.itemContainer,animation]}>
      <View style={style.item}>
        <Text style={style.title}>{title}</Text>
      </View>
    </Animated.View>
  );
}

Create a FlatListWrapper (to avoid the onViewableItemChange on fly error). By doing this, as long as you don't make changes to FlatListWrapper, you won't get the on the fly error:

// FlatListWrapper.js
const FlatListWrapper = (props) => {

  // useRef to avoid onViewableItemsChange on fly error
  const viewabilityConfig = useRef({
    // useRef to try to counter the view rerender thing
    itemVisiblePercentThreshold:80
  }).current;
  // wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
  const onViewChange = useCallback(props.onViewableItemsChanged,[])
  return (
      <View style={style.flatlistContainer}>
        <FlatList
          {...props}
          horizontal={true}
          onViewableItemsChanged={onViewChange}
        />
      </View>
  );
}
const style = StyleSheet.create({
 
  flatlistContainer:{
    borderWidth:1,
    borderColor:'red',
    width:'50%',
    height:40
  },
 
// main FlatList component
const FlatListAnimation = () => {

  // store the indices of the viewableItmes
  const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
  
  return (
    <SafeAreaView style={style.container}>
      <FlatListWrapper
        horizontal={true}
        //{/*give each data item an isViewable prop*/}
        data={DATA.map((item,i)=>{
          item.isViewable=viewableItemsIndices.find(ix=>ix == i)
          return item
        })}
        renderItem={item=><Item {...item}/>}
        keyExtractor={item => item.id}
        onViewableItemsChanged={({viewableItems, changed})=>{
          // set viewableItemIndices to the indices when view change
          setViewableItemsIndices(viewableItems.map(item=>item.index))
        }}
        //{/*config that decides when an item is viewable*/} 
        viewabilityConfig={{itemVisiblePercentThreshold:80}}
        extraData={viewableItemsIndices}
      />
     {/* Extra stuff that just tells you what items should be visible*/}
      <Text>Items that should be visible:</Text>
      {viewableItemsIndices.map(i=><Text>  {DATA[i].title}</Text>)}
    </SafeAreaView>
  );
}
const style = StyleSheet.create({
  container:{
    padding:10,
    alignItems:'center'
  },
  flatlistContainer:{
    borderWidth:1,
    borderColor:'red',
    width:'50%',
    height:40
  },
  item:{
    borderWidth:1,
    padding:5,
  },
  itemContainer:{
    padding:5,
  }
})

By wrapping your FlatList in a separate file, you won't encounter the "onViewableItemsChange on the fly" error as long as you don't modify FlatListWrapper.js

Upvotes: 1

Junius L
Junius L

Reputation: 16122

Use onViewableItemsChanged this is called when the items in the flatlist changes.

const handleViewableItemsChanged = (viewableItems, changed) => {}

<Flatlist
  ...
  onViewableItemsChanged={handleViewableItemsChanged}

Upvotes: 2

Related Questions