Skip to content

Commit

Permalink
add page transition animations
Browse files Browse the repository at this point in the history
- create screens for main and share
- use react navigation
- customize react navigation animation
  • Loading branch information
anchetaWern committed Jun 25, 2018
1 parent 76c7847 commit adb772d
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 93 deletions.
96 changes: 7 additions & 89 deletions App.js
@@ -1,101 +1,19 @@
import React, { Component } from "react";
import { View, Platform, Animated } from "react-native";
import { View, YellowBox } from "react-native";

import pokemon from "./src/data/pokemon";
import pokemon_stats from "./src/data/pokemon-stats";
import Root from "./Root";

import AnimatedHeader from "./src/components/AnimatedHeader";
import CardList from "./src/components/CardList";
import AnimatedModal from "./src/components/AnimatedModal";
import BigCard from "./src/components/BigCard";

import { HEADER_MAX_HEIGHT } from "./src/settings/layout";

import { getRandomInt } from "./src/lib/random";
YellowBox.ignoreWarnings([
"Warning: isMounted(...) is deprecated",
"Module RCTImageLoader"
]);

type Props = {};
export default class App extends Component<Props> {
state = {
isModalVisible: false
};

constructor(props) {
super(props);
this.pokemon_stats = [];
this.nativeScrollY = new Animated.Value(
Platform.OS === "ios" ? -HEADER_MAX_HEIGHT : 0
);
}

cardAction = () => {};

viewAction = (pokemon, image) => {
this.pokemon_stats = [];
pokemon_stats.forEach(item => {
this.pokemon_stats.push({
label: item,
value: getRandomInt(25, 150)
});
});

this.setState({
pokemon,
image,
stats: this.pokemon_stats,
isModalVisible: true
});
};

bookmarkAction = () => {};

shareAction = () => {};

closeModal = () => {
this.setState({
isModalVisible: false
});
};

render() {
let nativeScrollY = Animated.add(
this.nativeScrollY,
Platform.OS === "ios" ? HEADER_MAX_HEIGHT : 0
);

return (
<View style={styles.container}>
<AnimatedHeader title={"Poke-Gallery"} nativeScrollY={nativeScrollY} />
{this.nativeScrollY && (
<CardList
data={pokemon}
cardAction={this.cardAction}
viewAction={this.viewAction}
bookmarkAction={this.bookmarkAction}
shareAction={this.shareAction}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.nativeScrollY } } }],
{
useNativeDriver: true
}
)}
/>
)}

<AnimatedModal
title={"View Pokemon"}
visible={this.state.isModalVisible}
onClose={() => {
this.setState({
isModalVisible: false
});
}}
>
<BigCard
title={this.state.pokemon}
image={this.state.image}
data={this.state.stats}
/>
</AnimatedModal>
<Root />
</View>
);
}
Expand Down
53 changes: 53 additions & 0 deletions Root.js
@@ -0,0 +1,53 @@
import React from "react";
import { Animated, Easing } from "react-native";
import { createStackNavigator } from "react-navigation";

import MainScreen from "./src/screens/Main";
import ShareScreen from "./src/screens/Share";

const transitionConfig = () => {
return {
transitionSpec: {
duration: 300,
easing: Easing.bounce,
timing: Animated.timing,
useNativeDriver: true
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps;

const thisSceneIndex = scene.index;
const width = layout.initWidth;

const translateX = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex],
outputRange: [width, 0],
extrapolate: "clamp"
});

const opacity = position.interpolate({
inputRange: [thisSceneIndex - 1, thisSceneIndex - 0.5, thisSceneIndex],
outputRange: [0, 0.2, 1],
extrapolate: "clamp"
});

return { opacity, transform: [{ translateX }] };
}
};
};
const RootStack = createStackNavigator(
{
Main: {
screen: MainScreen
},
Share: {
screen: ShareScreen
}
},
{
initialRouteName: "Main",
transitionConfig
}
);

export default RootStack;
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -9,7 +9,8 @@
"dependencies": {
"react": "16.3.1",
"react-native": "0.55.4",
"react-native-vector-icons": "^4.6.0"
"react-native-vector-icons": "^4.6.0",
"react-navigation": "^2.5.2"
},
"devDependencies": {
"babel-jest": "23.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/components/AnimatedHeader.js
Expand Up @@ -31,7 +31,7 @@ const AnimatedHeader = ({ title, nativeScrollY }) => {

const titleTranslateY = nativeScrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE / 2, HEADER_SCROLL_DISTANCE],
outputRange: [25, 35, 15],
outputRange: [0, -10, -8],
extrapolate: "clamp"
});

Expand Down
14 changes: 12 additions & 2 deletions src/components/Card.js
Expand Up @@ -48,7 +48,11 @@ const Card = ({
}}
>
<Animated.View style={transformStyle}>
<Image source={item.pic} style={styles.thumbnail} />
<Image
source={item.pic}
style={styles.thumbnail}
resizeMode="contain"
/>
<Text style={styles.name}>{item.name}</Text>
<View style={styles.icons}>
<IconButton
Expand All @@ -59,7 +63,13 @@ const Card = ({
data={item}
/>
<IconButton icon="bookmark" onPress={bookmarkAction} data={item} />
<IconButton icon="share" onPress={shareAction} data={item} />
<IconButton
icon="share"
onPress={() => {
shareAction(item.name, item.full_pic);
}}
data={item}
/>
</View>
</Animated.View>
</TouchableWithoutFeedback>
Expand Down
37 changes: 37 additions & 0 deletions src/components/IconLabel.js
@@ -0,0 +1,37 @@
import React from "react";
import { Text, TouchableOpacity } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";

const IconLabel = ({ icon, label, bgColor }) => {
let backgroundColor = { backgroundColor: bgColor };
return (
<TouchableOpacity
onPress={this.share}
style={[styles.shareButton, backgroundColor]}
>
<Icon name={icon} style={styles.icon} size={30} color="#fff" />
<Text style={styles.label}>{label}</Text>
</TouchableOpacity>
);
};

const styles = {
shareButton: {
padding: 10,
marginBottom: 10,
flexDirection: "row",
justifyContent: "space-between"
},
icon: {
flex: 2
},
label: {
flex: 8,
marginTop: 5,
color: "#fff",
fontSize: 16,
fontWeight: "bold"
}
};

export default IconLabel;
128 changes: 128 additions & 0 deletions src/screens/Main.js
@@ -0,0 +1,128 @@
import React, { Component } from "react";
import { View, Platform, Animated } from "react-native";

import pokemon from "../data/pokemon";
import pokemon_stats from "../data/pokemon-stats";

import AnimatedHeader from "../components/AnimatedHeader";
import CardList from "../components/CardList";
import AnimatedModal from "../components/AnimatedModal";
import BigCard from "../components/BigCard";

import { HEADER_MAX_HEIGHT } from "../settings/layout.js";

import { getRandomInt } from "../lib/random";

type Props = {};
export default class Main extends Component<Props> {
state = {
isModalVisible: false
};

static navigationOptions = ({ navigation }) => {
return {
headerTitle: "",
headerStyle: {
elevation: 0,
shadowOpacity: 0,
backgroundColor: "#B4A608"
},
headerTitleStyle: {
color: "#FFF"
}
};
};

constructor(props) {
super(props);
this.nativeScrollY = new Animated.Value(
Platform.OS === "ios" ? -HEADER_MAX_HEIGHT : 0
);
}

cardAction = () => {};

viewAction = (pokemon, image) => {
this.setState({
pokemon,
image,
stats: this.getPokemonStats(),
isModalVisible: true
});
};

bookmarkAction = () => {};

shareAction = (pokemon, image) => {
this.props.navigation.navigate("Share");
};

closeModal = () => {
this.setState({
isModalVisible: false
});
};

getPokemonStats = () => {
let pokemon_stats_data = [];
pokemon_stats.forEach(item => {
pokemon_stats_data.push({
label: item,
value: getRandomInt(25, 150)
});
});

return pokemon_stats_data;
};

render() {
let nativeScrollY = Animated.add(
this.nativeScrollY,
Platform.OS === "ios" ? HEADER_MAX_HEIGHT : 0
);

return (
<View style={styles.container}>
<AnimatedHeader title={"Poke-Gallery"} nativeScrollY={nativeScrollY} />
{this.nativeScrollY && (
<CardList
data={pokemon}
cardAction={this.cardAction}
viewAction={this.viewAction}
bookmarkAction={this.bookmarkAction}
shareAction={this.shareAction}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.nativeScrollY } } }],
{
useNativeDriver: true
}
)}
/>
)}

<AnimatedModal
title={"View Pokemon"}
visible={this.state.isModalVisible}
onClose={() => {
this.setState({
isModalVisible: false
});
}}
>
<BigCard
title={this.state.pokemon}
image={this.state.image}
data={this.state.stats}
/>
</AnimatedModal>
</View>
);
}
}

const styles = {
container: {
flex: 1,
backgroundColor: "#fff"
}
};

0 comments on commit adb772d

Please sign in to comment.