Hello Devs
Today I would like to share a bit of item remove animation code (translate / scale animation) in react native.
Here's a glimpse of what I've prepared.
Suppose, you have a list of items in your project (very common example, which most of us deal with in our day to day projects).
export interface ItemEntity {
id: number;
taskTitle: string;
}
export const sampleItems: Array<ItemEntity> = [
{
id: 1,
taskTitle: "Breakfast",
},
{
id: 2,
taskTitle: "Lunch",
},
{
id: 3,
taskTitle: "Sports",
},
{
id: 4,
taskTitle: "Dinner",
},
];
And these items populated into a flat list for displaying inside a react native application
// state data items to be populated in flat list
const [dataItems, setDataItems] = useState<Array<ItemEntity>>(sampleItems);
// Handler function to remove the item (after animation ends)
const _handleAfterRemovedAction = (givenEntity: ItemEntity) => {
setDataItems(
dataItems.filter(
(itemIterated: ItemEntity) => itemIterated.id !== givenEntity.id,
),
);
};
// item in a list
const _renderItem = ({item, index}: {item: ItemEntity; index: number}) => {
return (
<ListViewItem
itemEntity={item}
onTaskCompletedWithAnimation={_handleAfterRemovedAction}
/>
);
};
// Flat List component with some styles
<FlatList
style={stylesToUse.listViewStyle}
contentContainerStyle={stylesToUse.listViewContentContainerStyle}
ListHeaderComponent={_renderHeader()}
data={dataItems}
keyExtractor={(item: ItemEntity, index: number) =>
`${item.id}_${item.taskTitle}_${index}`
}
renderItem={_renderItem}
ItemSeparatorComponent={_renderSeparator}
showsVerticalScrollIndicator={false}
/>
Now, the animation magic happens in our item component on the click of remove button.
Code Snippet
import {Animated} from 'react-native';
const _removeItemWithAnimation = () => {
Animated.parallel([
Animated.timing(translateAnim, {
// Negative Value: Translate to left, Positive Value: Translate to right
toValue: -1000,
duration: 800,
useNativeDriver: true,
}),
]).start(() => { /** on end of animation, this block executes, handle your removal logic */});
};
Full code
ListViewItem.tsx
import {Animated, Text, TouchableOpacity, View} from 'react-native';
import {ItemEntity} from '../../data/ItemEntity';
import {listViewItemStyles} from './styles/ListViewItem.styles';
export type PropsListViewItem = {
itemEntity: ItemEntity;
onTaskCompletedWithAnimation?: (givenItem: ItemEntity) => void;
};
export const ListViewItem = ({
itemEntity,
onTaskCompletedWithAnimation,
}: PropsListViewItem) => {
const stylesToUse = listViewItemStyles;
const translateAnim = new Animated.Value(0); // Initial position
// trigger remove animation
const _removeItemWithAnimation = () => {
Animated.parallel([
Animated.timing(translateAnim, {
// Negative Value: Translate to left, Positive Value: Translate to right
toValue: -1000,
duration: 800,
useNativeDriver: true,
}),
]).start(() => onTaskCompletedWithAnimation?.(itemEntity));
};
const _renderTitle = () => {
return (
<Text style={stylesToUse.titleTextStyle}>{itemEntity.taskTitle}</Text>
);
};
const _renderRemoveAction = () => {
return (
<TouchableOpacity
style={stylesToUse.deleteButtonContainer}
onPress={() => {
_removeItemWithAnimation();
}}>
<Text style={stylesToUse.deleteButtonTextStyle}>Remove</Text>
</TouchableOpacity>
);
};
return (
<Animated.View
style={[
stylesToUse.container,
{
transform: [{translateX: translateAnim}]
},
]}>
<View style={stylesToUse.row}>
{/* Task Title */}
{_renderTitle()}
{/* Remove button */}
{_renderRemoveAction()}
</View>
</Animated.View>
);
};
ListViewItem.styles.ts
import {StyleSheet} from 'react-native';
export const listViewItemStyles = StyleSheet.create({
container: {
backgroundColor: '#e1e1e1',
borderRadius: 8,
elevation: 1,
},
row: {
padding: 20,
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
titleTextStyle: {
flex: 1,
fontSize: 16,
fontWeight: '500',
color: '#212121',
},
deleteButtonContainer: {
paddingHorizontal: 12,
paddingVertical: 8,
backgroundColor: '#EF5350',
borderRadius: 24,
},
deleteButtonTextStyle: {
fontSize: 12,
color: '#ffffff',
},
});
The translation removal animation can be relevant in following use cases.
- Marking a task as done/incomplete.
- Remove an item from the list (we just did)
- Archiving an item etc.
BONUS
We can also combine the translate animation with scale animation.
const scaleAnim = new Animated.Value(1); // Initial scale value
const _removeItemWithAnimation = () => {
Animated.parallel([
Animated.timing(translateAnim, {
// Negative Value: Translate to left, Positive Value: Translate to right
toValue: -1000,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
// toValue 0 indicates the item will shrink to size 0
toValue: 0,
duration: 800,
useNativeDriver: true,
}),
]).start(() => onTaskCompletedWithAnimation?.(itemEntity));
};
And our animated view (in ListViewItem.tsx)
<Animated.View
style={[
stylesToUse.container,
{
transform: [{translateX: translateAnim}, {scale: scaleAnim}],
},
]}> ...
Play around with duration
and toValue
to experience yourself.
Or you can just use scale animation and omit translate animation based on your requirement.
Quick Bytes:
- Translation - Translation refers to moving of an item from one direction to the other or we can say position of an item changes entirely.
- Scale - Item's height/width updates. Either it's enlarged or it shrinks.
Happy coding.
Top comments (0)