Forked from eveningkid/react-native-animated_twitter-profile.jsx
Created
December 15, 2021 14:45
-
-
Save azmanabdlh/10242d28771e7c6a0b550ccba5adf132 to your computer and use it in GitHub Desktop.
React Native Animated: Twitter Profile Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Expo SDK41 | |
// expo-blur: ~9.0.3 | |
import React, { useRef } from 'react'; | |
import { | |
Animated, | |
Image, | |
ImageBackground, | |
ScrollView, | |
StatusBar, | |
StyleSheet, | |
Text, | |
View, | |
} from 'react-native'; | |
import { Feather } from '@expo/vector-icons'; | |
import { | |
SafeAreaProvider, | |
useSafeAreaInsets, | |
} from 'react-native-safe-area-context'; | |
import { BlurView } from 'expo-blur'; | |
function generateTweets(limit) { | |
return new Array(limit).fill(0).map((_, index) => { | |
const repetitions = Math.floor(Math.random() * 3) + 1; | |
return { | |
key: index.toString(), | |
text: 'Lorem ipsum dolor amet '.repeat(repetitions), | |
author: 'Arnaud', | |
tag: 'eveningkid', | |
}; | |
}); | |
} | |
const TWEETS = generateTweets(30); | |
const HEADER_HEIGHT_EXPANDED = 35; | |
const HEADER_HEIGHT_NARROWED = 90; | |
const PROFILE_PICTURE_URI = | |
'https://pbs.twimg.com/profile_images/975388677642715136/7Hw2MgQ2_400x400.jpg'; | |
const PROFILE_BANNER_URI = | |
'https://pbs.twimg.com/profile_banners/3296259169/1438473955/1500x500'; | |
const AnimatedImageBackground = Animated.createAnimatedComponent( | |
ImageBackground | |
); | |
const AnimatedBlurView = Animated.createAnimatedComponent(BlurView); | |
export default function WrappedApp() { | |
// Keeps notches away | |
return ( | |
<SafeAreaProvider> | |
<App /> | |
</SafeAreaProvider> | |
); | |
} | |
function App() { | |
const insets = useSafeAreaInsets(); | |
const scrollY = useRef(new Animated.Value(0)).current; | |
return ( | |
<View style={styles.container}> | |
<StatusBar barStyle="light-content" /> | |
{/* Back button */} | |
<View | |
style={{ | |
zIndex: 2, | |
position: 'absolute', | |
top: insets.top + 10, | |
left: 20, | |
backgroundColor: 'rgba(0, 0, 0, 0.6)', | |
height: 30, | |
width: 30, | |
borderRadius: 15, | |
alignItems: 'center', | |
justifyContent: 'center', | |
}} | |
> | |
<Feather name="chevron-left" color="white" size={26} /> | |
</View> | |
{/* Refresh arrow */} | |
<Animated.View | |
style={{ | |
zIndex: 2, | |
position: 'absolute', | |
top: insets.top + 13, | |
left: 0, | |
right: 0, | |
alignItems: 'center', | |
opacity: scrollY.interpolate({ | |
inputRange: [-20, 0], | |
outputRange: [1, 0], | |
}), | |
transform: [ | |
{ | |
rotate: scrollY.interpolate({ | |
inputRange: [-45, -35], | |
outputRange: ['180deg', '0deg'], | |
extrapolate: 'clamp', | |
}), | |
}, | |
], | |
}} | |
> | |
<Feather name="arrow-down" color="white" size={25} /> | |
</Animated.View> | |
{/* Name + tweets count */} | |
<Animated.View | |
style={{ | |
zIndex: 2, | |
position: 'absolute', | |
top: insets.top + 6, | |
left: 0, | |
right: 0, | |
alignItems: 'center', | |
opacity: scrollY.interpolate({ | |
inputRange: [90, 110], | |
outputRange: [0, 1], | |
}), | |
transform: [ | |
{ | |
translateY: scrollY.interpolate({ | |
inputRange: [90, 120], | |
outputRange: [30, 0], | |
extrapolate: 'clamp', | |
}), | |
}, | |
], | |
}} | |
> | |
<Text style={[styles.text, styles.username]}>Arnaud</Text> | |
<Text style={[styles.text, styles.tweetsCount]}> | |
379 tweets | |
</Text> | |
</Animated.View> | |
{/* Banner */} | |
<AnimatedImageBackground | |
source={{ | |
uri: PROFILE_BANNER_URI, | |
}} | |
style={{ | |
position: 'absolute', | |
left: 0, | |
right: 0, | |
height: HEADER_HEIGHT_EXPANDED + HEADER_HEIGHT_NARROWED, | |
transform: [ | |
{ | |
scale: scrollY.interpolate({ | |
inputRange: [-200, 0], | |
outputRange: [5, 1], | |
extrapolateLeft: 'extend', | |
extrapolateRight: 'clamp', | |
}), | |
}, | |
], | |
}} | |
> | |
<AnimatedBlurView | |
tint="dark" | |
intensity={96} | |
style={{ | |
...StyleSheet.absoluteFillObject, | |
zIndex: 2, | |
opacity: scrollY.interpolate({ | |
inputRange: [-50, 0, 50, 100], | |
outputRange: [1, 0, 0, 1], | |
}), | |
}} | |
/> | |
</AnimatedImageBackground> | |
{/* Tweets/profile */} | |
<Animated.ScrollView | |
showsVerticalScrollIndicator={false} | |
onScroll={Animated.event( | |
[ | |
{ | |
nativeEvent: { | |
contentOffset: { y: scrollY }, | |
}, | |
}, | |
], | |
{ useNativeDriver: true } | |
)} | |
style={{ | |
zIndex: 3, | |
marginTop: HEADER_HEIGHT_NARROWED, | |
paddingTop: HEADER_HEIGHT_EXPANDED, | |
}} | |
> | |
<View | |
style={[styles.container, { backgroundColor: 'black' }]} | |
> | |
<View | |
style={[ | |
styles.container, | |
{ | |
paddingHorizontal: 20, | |
}, | |
]} | |
> | |
<Animated.Image | |
source={{ | |
uri: PROFILE_PICTURE_URI, | |
}} | |
style={{ | |
width: 75, | |
height: 75, | |
borderRadius: 40, | |
borderWidth: 4, | |
borderColor: 'black', | |
marginTop: -30, | |
transform: [ | |
{ | |
scale: scrollY.interpolate({ | |
inputRange: [0, HEADER_HEIGHT_EXPANDED], | |
outputRange: [1, 0.6], | |
extrapolate: 'clamp', | |
}), | |
}, | |
{ | |
translateY: scrollY.interpolate({ | |
inputRange: [0, HEADER_HEIGHT_EXPANDED], | |
outputRange: [0, 16], | |
extrapolate: 'clamp', | |
}), | |
}, | |
], | |
}} | |
/> | |
<Text | |
style={[ | |
styles.text, | |
{ | |
fontSize: 24, | |
fontWeight: 'bold', | |
marginTop: 10, | |
}, | |
]} | |
> | |
Arnaud | |
</Text> | |
<Text | |
style={[ | |
styles.text, | |
{ | |
fontSize: 15, | |
color: 'gray', | |
marginBottom: 15, | |
}, | |
]} | |
> | |
@eveningkid | |
</Text> | |
<Text | |
style={[ | |
styles.text, | |
{ marginBottom: 15, fontSize: 15 }, | |
]} | |
> | |
Same @ on every social media | |
</Text> | |
{/* Profile stats */} | |
<View | |
style={{ | |
flexDirection: 'row', | |
marginBottom: 15, | |
}} | |
> | |
<Text | |
style={[ | |
styles.text, | |
{ | |
fontWeight: 'bold', | |
marginRight: 10, | |
}, | |
]} | |
> | |
70{' '} | |
<Text | |
style={{ | |
color: 'gray', | |
fontWeight: 'normal', | |
}} | |
> | |
Following | |
</Text> | |
</Text> | |
<Text style={[styles.text, { fontWeight: 'bold' }]}> | |
106{' '} | |
<Text | |
style={{ | |
color: 'gray', | |
fontWeight: 'normal', | |
}} | |
> | |
Followers | |
</Text> | |
</Text> | |
</View> | |
</View> | |
<View style={styles.container}> | |
{TWEETS.map((item, index) => ( | |
<View key={item.key} style={styles.tweet}> | |
<Image | |
source={{ | |
uri: PROFILE_PICTURE_URI, | |
}} | |
style={{ | |
height: 50, | |
width: 50, | |
borderRadius: 25, | |
marginRight: 10, | |
}} | |
/> | |
<View style={styles.container}> | |
<Text | |
style={[ | |
styles.text, | |
{ | |
fontWeight: 'bold', | |
fontSize: 15, | |
}, | |
]} | |
> | |
{item.author}{' '} | |
<Text | |
style={{ | |
color: 'gray', | |
fontWeight: 'normal', | |
}} | |
> | |
@{item.tag} · {index + 1}d | |
</Text> | |
</Text> | |
<Text style={[styles.text, { fontSize: 15 }]}> | |
{item.text} | |
</Text> | |
</View> | |
</View> | |
))} | |
</View> | |
</View> | |
</Animated.ScrollView> | |
</View> | |
); | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1, | |
}, | |
text: { | |
color: 'white', | |
}, | |
username: { | |
fontSize: 18, | |
fontWeight: 'bold', | |
marginBottom: -3, | |
}, | |
tweetsCount: { | |
fontSize: 13, | |
}, | |
tweet: { | |
flexDirection: 'row', | |
paddingVertical: 10, | |
paddingHorizontal: 20, | |
borderTopWidth: StyleSheet.hairlineWidth, | |
borderTopColor: 'rgba(255, 255, 255, 0.25)', | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment