Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add list itemLayoutAnimation documentation page and example #6279

Merged
merged 14 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { memo, useCallback, useState } from 'react';
import type { ListRenderItem } from 'react-native';
import {
Dimensions,
Pressable,
SafeAreaView,
StyleSheet,
Text,
TouchableOpacity,
} from 'react-native';
import Animated, {
LayoutAnimationConfig,
LinearTransition,
} from 'react-native-reanimated';

const ITEMS = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];

type ListItemProps = {
id: string;
text: string;
onPress: (id: string) => void;
};

const ListItem = memo(function ({ id, text, onPress }: ListItemProps) {
return (
<Pressable onPress={() => onPress(id)} style={styles.listItem}>
<Text style={styles.itemText}>{text}</Text>
</Pressable>
);
});

export default function ListItemLayoutAnimation() {
const [items, setItems] = useState(ITEMS);

const handlePress = useCallback((id: string) => {
setItems((prevItems) => prevItems.filter((item) => item !== id));
}, []);

const renderItem = useCallback<ListRenderItem<string>>(
({ item }) => <ListItem id={item} text={item} onPress={handlePress} />,
[handlePress]
);

const findItemName = useCallback(() => {
let i = 1;
while (items.includes(`Item ${i}`)) {
i++;
}
return `Item ${i}`;
}, [items]);

const reorderItems = useCallback(() => {
setItems((prevItems) => {
const newItems = [...prevItems];
newItems.sort(() => Math.random() - 0.5);
return newItems;
});
}, []);

const resetOrder = useCallback(() => {
setItems((prevItems) => {
const newItems = [...prevItems];
newItems.sort((a, b) => {
const aNum = parseInt(a.match(/\d+$/)![0], 10);
const bNum = parseInt(b.match(/\d+$/)![0], 10);
return aNum - bNum;
});
return newItems;
});
}, []);

const transition = LinearTransition;

return (
// Skip initial entering animation
<LayoutAnimationConfig skipEntering>
<SafeAreaView style={styles.container}>
<Animated.FlatList
style={styles.list}
data={items}
renderItem={renderItem}
keyExtractor={(item) => item}
contentContainerStyle={styles.contentContainer}
itemLayoutAnimation={transition}
layout={transition}
/>
<Animated.View style={styles.bottomMenu} layout={transition}>
<Text style={styles.infoText}>Press an item to remove it</Text>
<TouchableOpacity
style={styles.button}
onPress={() => setItems([...items, findItemName()])}>
<Text style={styles.buttonText}>Add item</Text>
</TouchableOpacity>
<Animated.View style={styles.row} layout={transition}>
<TouchableOpacity style={styles.button} onPress={reorderItems}>
<Text style={styles.buttonText}>Reorder</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={resetOrder}>
<Text style={styles.buttonText}>Reset</Text>
</TouchableOpacity>
</Animated.View>
</Animated.View>
</SafeAreaView>
</LayoutAnimationConfig>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
contentContainer: {
padding: 16,
gap: 16,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
},
list: {
flexGrow: 0,
maxHeight: Dimensions.get('window').height - 250,
},
listItem: {
padding: 20,
backgroundColor: '#ad8ee9',
shadowColor: '#000',
shadowOpacity: 0.05,
},
itemText: {
color: 'white',
fontSize: 22,
},
bottomMenu: {
alignItems: 'center',
flex: 1,
},
button: {
paddingTop: 20,
width: 100,
alignItems: 'center',
},
infoText: {
paddingTop: 16,
color: '#222534',
fontSize: 18,
},
buttonText: {
fontSize: 18,
fontWeight: 'bold',
color: '#b59aeb',
},
});
5 changes: 5 additions & 0 deletions apps/common-app/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ import BorderRadiiExample from './SharedElementTransitions/BorderRadii';
import FreezingShareablesExample from './ShareableFreezingExample';
import TabNavigatorExample from './SharedElementTransitions/TabNavigatorExample';
import StrictDOMExample from './StrictDOMExample';
import ListItemLayoutAnimation from './LayoutAnimations/ListItemLayoutAnimation';

interface Example {
icon?: string;
Expand Down Expand Up @@ -669,6 +670,10 @@ export const EXAMPLES: Record<string, Example> = {
title: '[LA] Reactions counter',
screen: ReactionsCounterExample,
},
ListItemLayoutAnimation: {
title: '[LA] List item layout animation',
screen: ListItemLayoutAnimation,
},
SwipeableList: {
title: '[LA] Swipeable list',
screen: SwipeableList,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
sidebar_position: 6
title: List Layout Animations
sidebar_label: List Layout Animations
---

`itemLayoutAnimation` lets you define the animation that will be used when list items layout changes. You can use one of the predefined [layout transitions](/docs/layout-animations/layout-transitions#predefined-transitions) like `LinearTransition` or create your own [custom layout transition](/docs/layout-animations/custom-animations#custom-layout-transition).

## Example

<Row>

<ThemedVideo
sources={{
light: '/recordings/layout-animations/listitem_light.mov',
dark: '/recordings/layout-animations/listitem_dark.mov',
}}
/>

<div style={{flexGrow: 1}}>

```jsx
import Animated, { LinearTransition } from 'react-native-reanimated';

function App() {
return (
<Animated.FlatList
data={data}
renderItem={renderItem}
// highlight-next-line
itemLayoutAnimation={LinearTransition}
/>
);
}
```

</div>

</Row>

## Remarks

- `itemLayoutAnimation` works only with a single-solumn `Animated.FlatList`,
- it does not work when `numColumns` is greater than 1,

## Platform compatibility

<div className="platform-compatibility">

| Android | iOS | Web |
| ------- | --- | --- |
| ✅ | ✅ | ✅ |

</div>
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface ReanimatedFlatListPropsWithLayout<T>
extends AnimatedProps<FlatListProps<T>> {
/**
* Lets you pass layout animation directly to the FlatList item.
* (Works only with a single-column Animated.FlatList - when numColumns is not greater than 1)
*/
itemLayoutAnimation?: ILayoutAnimationBuilder;
/**
Expand Down