nathvarun / apple-music-ui-pan-animation-tutorial Goto Github PK
View Code? Open in Web Editor NEWLearn How to Create the Now playing Toolbar Animation of Apple Music in React Native
License: MIT License
Learn How to Create the Now playing Toolbar Animation of Apple Music in React Native
License: MIT License
MY CODE ๐๐ป
`import React, {useState, useEffect, useRef} from 'react';
import {
View,
Text,
StyleSheet,
Dimensions,
Animated,
PanResponder,
ScrollView,
Image,
Slider,
} from 'react-native';
const {width, height} = Dimensions.get('window');
import Icon from 'react-native-vector-icons/Ionicons';
const App = (props) => {
const [isScrollEnabled, setIsScrollEnabled] = useState(false);
const animation = new Animated.ValueXY({x: 0, y: height - 90});
const scrollOffset = 0;
const pan = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
if (
(isScrollEnabled && scrollOffset <= 0 && gestureState.dy > 0) ||
(!isScrollEnabled && gestureState.dy < 0)
) {
return true;
} else {
return false;
}
},
onPanResponderGrant: (evt, gestureState) => {
animation.extractOffset();
},
onPanResponderMove: (evt, gestureState) => {
animation.setValue({x: 0, y: gestureState.dy});
},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.moveY > height - 120) {
Animated.spring(animation.y, {
toValue: 0,
tension: 1,
useNativeDriver: false,
}).start();
} else if (gestureState.moveY < 120) {
Animated.spring(animation.y, {
toValue: 0,
tension: 1,
useNativeDriver: false,
}).start();
} else if (gestureState.dy < 0) {
setIsScrollEnabled(false);
Animated.spring(animation.y, {
toValue: -height + 120,
tension: 1,
useNativeDriver: false,
}).start();
} else if (gestureState.dy > 0) {
setIsScrollEnabled(false);
Animated.spring(animation.y, {
toValue: height - 120,
tension: 1,
useNativeDriver: false,
}).start();
}
},
}),
).current;
const [panResponder, setPanResponder] = useState(pan);
useEffect(() => {
setPanResponder(pan);
}, []);
const animatedHeight = {
transform: animation.getTranslateTransform(),
};
const animatedImageHeight = animation.y.interpolate({
inputRange: [0, height - 90],
outputRange: [200, 32],
extrapolate: 'clamp',
});
const animatedSongTitleOpacity = animation.y.interpolate({
inputRange: [0, height - 500, height - 90],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
});
const animatedImageMarginLeft = animation.y.interpolate({
inputRange: [0, height - 90],
outputRange: [width / 2 - 100, 10],
extrapolate: 'clamp',
});
const animatedSongDetailsOpacity = animation.y.interpolate({
inputRange: [0, height - 500, height - 90],
outputRange: [1, 0, 0],
extrapolate: 'clamp',
});
const animatedHeaderHeight = animation.y.interpolate({
inputRange: [0, height - 90],
outputRange: [height / 2, 90],
extrapolate: 'clamp',
});
const animatedBackgroundColor = animation.y.interpolate({
inputRange: [0, height - height],
outputRange: ['red', 'pink'],
extrapolate: 'extend',
});
return (
<Animated.View style={{flex: 1, backgroundColor: animatedBackgroundColor}}>
<Animated.View
{...panResponder.panHandlers}
style={[
animatedHeight,
{
position: 'absolute',
left: 0,
right: 0,
zIndex: 10,
backgroundColor: '#d3d3',
height: height,
},
]}>
<ScrollView
scrollEnabled={isScrollEnabled}
scrollEventThrottle={16}
onScroll={(event) => {
scrollOffset = event.nativeEvent.contentOffset.y;
}}>
<Animated.View
style={{
height: animatedHeaderHeight,
borderTopWidth: 1,
borderTopColor: '#ebe5e5',
flexDirection: 'row',
alignItems: 'center',
}}>
<View style={{flex: 4, flexDirection: 'row', alignItems: 'center'}}>
<Animated.View
style={{
height: animatedImageHeight,
width: animatedImageHeight,
marginLeft: animatedImageMarginLeft,
}}>
<Image
style={{flex: 1, width: null, height: null}}
source={{
uri:
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTfCrFGpWG5cxEH5GpVjNU8wFutQebq-sn1ww&usqp=CAU',
}}
/>
</Animated.View>
<Animated.Text
style={{
opacity: animatedSongTitleOpacity,
fontSize: 18,
paddingLeft: 10,
}}>
Music Player Is (On)
</Animated.Text>
<Animated.View
style={{
opacity: animatedSongTitleOpacity,
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
}}>
</Animated.View>
</Animated.View>
<Animated.View
style={{
height: animatedHeaderHeight,
opacity: animatedSongDetailsOpacity,
}}>
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'flex-end',
}}>
<Text style={{fontWeight: 'bold', fontSize: 22}}>
Music Player Is (On)
</Text>
<Text style={{fontSize: 18, color: '#fa95ed'}}>
despacito despacito
</Text>
</View>
<View style={{height: 40, width: width, alignItems: 'center'}}>
<Slider
style={{width: 300}}
step={1}
minimumValue={18}
maximumValue={71}
value={18}
/>
</View>
<View
style={{
flex: 2,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
}}>
<Icon name="md-rewind" size={40} />
<Icon name="md-pause" size={50} />
<Icon name="md-fastforward" size={40} />
</View>
</Animated.View>
</ScrollView>
</Animated.View>
</Animated.View>
);
};
export default App;`
in android animation is working fine but as soon as I scroll down the scrollview , animation moves slowly.
Hi,
Thanks for this great tutorial. I have followed along with the code and unfortunately found that the animation does not work as intended/is not smooth whatsoever when run on a native device (or simulator) - but works just as you would expect on expo.
As you can see from the gif, despite swiping up on the bar from the bottom a considerable distance - the height only increases by a small amount.
I have included the code below (the same as the tutorial).
import React, { Component } from 'react';
import {
View,
Text,
Dimensions,
Animated,
Image,
PanResponder,
Slider,
ScrollView,
} from 'react-native';
import Ionicons from "react-native-vector-icons/Ionicons";
const SCREEN_HEIGHT = Dimensions.get('window').height;
const SCREEN_WIDTH = Dimensions.get('window').width;
export default class AppleMusicUI extends Component {
state = {
isScrollEnabled: false,
};
componentWillMount() {
this.scrollOffset = 0;
this.animation = new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 90 });
this.PanResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
if (
(this.state.isScrollEnabled &&
this.scrollOffset <= 0 &&
gestureState.dy > 0) ||
(!this.state.isScrollEnabled && gestureState.dy < 0)
) {
return true;
} else {
return false;
}
},
onPanResponderGrant: () => {
this.animation.extractOffset();
},
onPanResponderMove: (evt, gestureState) => {
this.animation.setValue({ x: 0, y: gestureState.dy });
},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.moveY > SCREEN_HEIGHT - 110) {
Animated.spring(this.animation.y, {
toValue: 0,
tension: 1,
}).start();
} else if (gestureState.moveY < 110) {
Animated.spring(this.animation.y, {
toValue: 0,
tension: 1,
}).start();
} else if (gestureState.dy < 0) {
this.setState({ isScrollEnabled: true });
Animated.spring(this.animation.y, {
toValue: -SCREEN_HEIGHT + 110,
tension: 1,
}).start();
} else if (gestureState.dy > 0) {
this.setState({ isScrollEnabled: false });
Animated.spring(this.animation.y, {
toValue: SCREEN_HEIGHT - 110,
tension: 1,
}).start();
}
},
});
}
render() {
const animatedHeight = {
transform: this.animation.getTranslateTransform(),
};
const animatedImageHeight = this.animation.y.interpolate({
inputRange: [0, SCREEN_HEIGHT - 90],
outputRange: [200, 32],
extrapolate: 'clamp',
});
const animatedSongTitleOpacity = this.animation.y.interpolate({
inputRange: [0, SCREEN_HEIGHT - 500, SCREEN_HEIGHT - 90],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
});
const animatedImageMarginLeft = this.animation.y.interpolate({
inputRange: [0, SCREEN_HEIGHT - 90],
outputRange: [SCREEN_WIDTH / 2 - 100, 10],
extrapolate: 'clamp',
});
const animatedHeaderHeight = this.animation.y.interpolate({
inputRange: [0, SCREEN_HEIGHT - 90],
outputRange: [SCREEN_HEIGHT / 2, 90],
extrapolate: 'clamp',
});
const animatedSongDetailsOpacity = this.animation.y.interpolate({
inputRange: [0, SCREEN_HEIGHT - 500, SCREEN_HEIGHT - 90],
outputRange: [1, 0, 0],
extrapolate: 'clamp',
});
const animatedBackgroundColor = this.animation.y.interpolate({
inputRange: [0, SCREEN_HEIGHT - 90],
outputRange: ['rgba(0,0,0,0.5)', 'white'],
extrapolate: 'clamp',
});
return (
<Animated.View
style={{ flex: 1, backgroundColor: animatedBackgroundColor }}>
<Animated.View
{...this.PanResponder.panHandlers}
style={[
animatedHeight,
{
position: 'absolute',
left: 0,
right: 0,
zIndex: 10,
backgroundColor: 'white',
height: SCREEN_HEIGHT,
},
]}>
<ScrollView
scrollEnabled={this.state.isScrollEnabled}
scrollEventThrottle={16}
onScroll={event => {
this.scrollOffset = event.nativeEvent.contentOffset.y;
}}>
<Animated.View
style={{
height: animatedHeaderHeight,
borderTopWidth: 1,
borderTopColor: '#ebe5e5',
flexDirection: 'row',
alignItems: 'center',
}}>
<View
style={{ flex: 4, flexDirection: 'row', alignItems: 'center' }}>
<Animated.View
style={{
height: animatedImageHeight,
width: animatedImageHeight,
marginLeft: animatedImageMarginLeft,
}}>
<Image
style={{
flex: 1,
width: null,
height: null,
}}
source={require('../../assets/Logo.png')}
/>
</Animated.View>
<Animated.Text
style={{
opacity: animatedSongTitleOpacity,
fontSize: 18,
paddingLeft: 10,
}}>
Hotel California(Live)
</Animated.Text>
</View>
<Animated.View
style={{
opacity: animatedSongTitleOpacity,
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
}}>
<Ionicons name="md-pause" size={32} />
<Ionicons name="md-play" size={32} />
</Animated.View>
</Animated.View>
<Animated.View
style={{
height: animatedHeaderHeight,
opacity: animatedSongDetailsOpacity,
}}>
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'flex-end',
}}>
<Text style={{ fontWeight: 'bold', fontSize: 22 }}>
Hotel California (Live)
</Text>
<Text style={{ fontSize: 18, color: '#fa95ed' }}>
Eagles - Hell Freezes Over
</Text>
</View>
<View
style={{
height: 40,
width: SCREEN_WIDTH,
alignItems: 'center',
}}>
<Slider
style={{ width: 300 }}
step={1}
minimumValue={18}
maximumValue={71}
value={18}
/>
</View>
<View
style={{
flex: 2,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
}}>
<Ionicons name="md-rewind" size={40} />
<Ionicons name="md-pause" size={50} />
<Ionicons name="md-fastforward" size={40} />
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingBottom: 20,
}}>
<Ionicons
name="md-add"
size={32}
style={{ color: '#fa95ed' }}
/>
<Ionicons
name="md-more"
size={32}
style={{ color: '#fa95ed' }}
/>
</View>
</Animated.View>
<View style={{ height: 1000 }} />
</ScrollView>
</Animated.View>
</Animated.View>
);
}
}
Thank you in advance!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.