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) => {
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved
setItems((prevItems) => prevItems.filter((item) => item !== id));
}, []);

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

const findItemName = useCallback(() => {
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved
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) => {
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved
const aNum = parseInt(a.match(/\d+$/)![0], 10);
const bNum = parseInt(b.match(/\d+$/)![0], 10);
tjzel marked this conversation as resolved.
Show resolved Hide resolved
return aNum - bNum;
});
return newItems;
});
}, []);

const transition = LinearTransition;

return (
// Skip initial entering animation
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved
<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
tjzel marked this conversation as resolved.
Show resolved Hide resolved
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).
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved

## 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,
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved

## 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)
MatiPl01 marked this conversation as resolved.
Show resolved Hide resolved
*/
itemLayoutAnimation?: ILayoutAnimationBuilder;
/**
Expand Down