software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
8.86k stars 1.29k forks source link

[2.3.0-alpha.3] Layout animation does not work inside `FlatList` #2424

Closed SimpleCreations closed 3 years ago

SimpleCreations commented 3 years ago

Description

Participant list example when using FlatList:

https://user-images.githubusercontent.com/12449725/133289806-321b720f-6494-4034-a451-6b1abb29d094.mp4

Participant list example when using ScrollView:

https://user-images.githubusercontent.com/12449725/133290050-a4015ded-de8c-47ed-a1e2-fb4c48ea828a.mp4

Expected behavior

When using FlatList, layout={Layout.springify()} causes the list elements to slide into their new positions with a spring animation.

Actual behavior & steps to reproduce

When using FlatList, layout={Layout.springify()} has no effect.

Snack or minimal code example

I've modified this file in the example app:

https://github.com/software-mansion/react-native-reanimated/blob/master/Example/src/LayoutReanimation/AnimatedList.tsx

@@ -3,9 +3,10 @@ import {
   Button,
   View,
   Text,
-  ScrollView,
   TextInput,
   StyleSheet,
+  FlatList,
+  ListRenderItem,
 } from 'react-native';
 import Animated, {
   Layout,
@@ -90,17 +91,20 @@ export default function AnimatedListExample(): React.ReactElement {
     );
   };

+  const renderItem: ListRenderItem<EventParticipant> = ({item}) => (
+    <Participant
+        name={item.name}
+        onRemove={() => removeParticipant(item.id)}
+    />
+  );
+
   return (
     <View style={[styles.listView]}>
-      <ScrollView style={[{ width: '100%' }]}>
-        {participantList.map((participant) => (
-          <Participant
-            key={participant.id}
-            name={participant.name}
-            onRemove={() => removeParticipant(participant.id)}
-          />
-        ))}
-      </ScrollView>
+      <FlatList
+          style={[{ width: '100%' }]}
+          data={participantList}
+          renderItem={renderItem}
+          keyExtractor={({id}) => id} />

       <View style={[styles.bottomRow]}>
         <View style={[styles.textInput]}>

Package versions

Affected platforms

github-actions[bot] commented 3 years ago

Issue validator

The issue is valid!

Szymon20000 commented 3 years ago

It's not really a problem with our Layout Animations. The problem is wrapping the elements into additional views by FlatList. From Reanimated perspective, a Participant component has always the same layout relative to its parent which is that additional View added by a FlatList. We will think about a solution to this issue.

piaskowyk commented 3 years ago

[temporary solution/simple hack] If you want to use Layout animation for FlatList you can apply this patch for the FlatList: https://patch-diff.githubusercontent.com/raw/piaskowyk/react-native/pull/1.patch

From 7a1ff47b212177cd8b018f6345d05565e5694cf4 Mon Sep 17 00:00:00 2001
From: Krzysztof Piaskowy <krzysztof.piaskowy@swmansion.com>
Date: Tue, 21 Sep 2021 00:00:54 +0200
Subject: [PATCH] List Layout patch

---
 Libraries/Lists/VirtualizedList.js | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js
index 2648cc3e4bb0..fd174ca3c019 100644
--- a/Libraries/Lists/VirtualizedList.js
+++ b/Libraries/Lists/VirtualizedList.js
@@ -21,6 +21,8 @@ const flattenStyle = require('../StyleSheet/flattenStyle');
 const infoLog = require('../Utilities/infoLog');
 const invariant = require('invariant');

+const Reanimated = require('../../../react-native-reaniamted/lib/Animated').default;
+
 import {
   keyExtractor as defaultKeyExtractor,
   computeWindowedRenderLimits,
@@ -2070,10 +2072,10 @@ class CellRenderer extends React.Component<
       /* $FlowFixMe[incompatible-type-arg] (>=0.89.0 site=react_native_fb) *
         This comment suppresses an error found when Flow v0.89 was deployed. *
         To see the error, delete this comment and run Flow. */
-      <View style={cellStyle} onLayout={onLayout}>
+      <Reanimated.View layout={element.props.layout} style={cellStyle} onLayout={onLayout}>
         {element}
         {itemSeparator}
-      </View>
+      </Reanimated.View>
     ) : (
       <CellRendererComponent
         {...this.props}

And add layout property to <Participant> component, something like this:

<Participant
  layout={Layout.springify()} // <-- add here
  name={item.name}
  onRemove={() => removeParticipant(item.id)}
/>
SimpleCreations commented 3 years ago

Thank you! Will the solution be a part of Reanimated without requiring a manual patch?

piaskowyk commented 3 years ago

We are considering to create own simple modification of FlatList, but unfortunately, I don't know when yet.

CallumHemsley commented 2 years ago

We are considering to create own simple modification of FlatList, but unfortunately, I don't know when yet.

Would be really interested in helping make this happen. Perhaps we could discuss what would be required for this to happen? :)

nicholash747 commented 2 years ago

An alternative to @piaskowyk 's temporary solution as I prefer not modifying the libs.

const createCellRenderer = (injectedProps?: AnimateProps<ViewProps>) => {
  const cellRenderer: React.FC<{
    style: StyleProp<ViewStyle>;
    onLayout: (event: LayoutChangeEvent) => void;
  }> = props => {
    return (
      <Animated.View
        {...injectedProps}
        style={props.style}
        onLayout={props.onLayout}>
        {props.children}
      </Animated.View>
    );
  };

  return cellRenderer;
};

interface ReanimatedFlatlistProps<ItemT extends {}>
  extends FlatListProps<ItemT> {
  animatedViewProps?: AnimateProps<ViewProps>;
}

type ReanimatedFlatListFC<T = any> = React.FC<ReanimatedFlatlistProps<T>>;

export const ReanimatedFlatlist: ReanimatedFlatListFC = ({
  animatedViewProps,
  ...restProps
}) => {
  const cellRenderer = React.useMemo(
    () => createCellRenderer(animatedViewProps),
    [animatedViewProps],
  );
  return <FlatList {...restProps} CellRendererComponent={cellRenderer} />;
};

Needs some optimization I'm sure. The animatedViewProps can't change between renders i.e. use memo.

e.g.

const cellAnimation = {
  entering: FadeInUp,
  exiting: FadeOutUp,
  layout: Layout.duration(1000),
};

const component = () => {
  return <ReanimatedFlatlist
      animatedViewProps={cellAnimation}
      ...
}

Issue I'm facing that with either of the solutions, child components that enter/exit have animation applied but don't seem to follow layout e.g. setting layout={Layout.duration(1000)}. EDIT: Never mind, this seems to be a misunderstanding of how the API works on my part :D

@piaskowyk shouldn't this issue remain open until a long term fix is in place?

piaskowyk commented 2 years ago

@nicholash747 thanks for the smart idea! I prepared PR based on your comment here - https://github.com/software-mansion/react-native-reanimated/pull/2674

SimpleCreations commented 2 years ago

@piaskowyk are you planning to add support for SectionLists as well?

piaskowyk commented 2 years ago

There is the same problem?

SimpleCreations commented 2 years ago

@piaskowyk yes. When I reported this issue I actually had it when using a SectionList.

valeriiamykhalova commented 2 years ago

Hi, @piaskowyk! Are you planning to create your own modification of SectionList component in the nearest future? It has a similar issue as FlatList. Will be really helpful to me 🙂

Thanks!

MarcHbb commented 1 year ago

Any news on SectionList support ? :)