Skip to content
This repository was archived by the owner on Nov 26, 2019. It is now read-only.

Commit 5d25053

Browse files
committed
Add a simple animated header
1 parent 279ef91 commit 5d25053

7 files changed

Lines changed: 271 additions & 54 deletions

File tree

src/components/Card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,15 +387,15 @@ export default class Card extends React.Component<Props> {
387387

388388
const styles = StyleSheet.create({
389389
container: {
390-
flex: 1
390+
flex: 1,
391391
},
392392
card: {
393393
...StyleSheet.absoluteFillObject,
394394
shadowOffset: { width: -1, height: 1 },
395395
shadowRadius: 5,
396396
shadowColor: '#000',
397397
backgroundColor: 'white',
398-
elevation: 2
398+
elevation: 2,
399399
},
400400
overlay: {
401401
...StyleSheet.absoluteFillObject,
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import * as React from 'react';
2+
import {
3+
View,
4+
StyleSheet,
5+
Platform,
6+
StatusBar,
7+
StyleProp,
8+
ViewStyle,
9+
} from 'react-native';
10+
import Animated from 'react-native-reanimated';
11+
import HeaderSheet from './HeaderSheet';
12+
import { Route, Layout } from '../Stack';
13+
import HeaderAnimatedItem, {
14+
HeaderAnimationPreset,
15+
InterpolationProps,
16+
} from './HeaderAnimatedItem';
17+
18+
type Scene<T extends Route> = {
19+
title: string;
20+
route: T;
21+
progress: Animated.Node<number>;
22+
};
23+
24+
type Props<T extends Route> = {
25+
layout: Layout;
26+
onGoBack: (props: { route: T }) => void;
27+
preset: HeaderAnimationPreset;
28+
scenes: Scene<T>[];
29+
style?: StyleProp<ViewStyle>;
30+
};
31+
32+
const { interpolate, multiply } = Animated;
33+
34+
const FadePreset: HeaderAnimationPreset = {
35+
styleInterpolator: ({ current, next }: InterpolationProps) => {
36+
const progress = next
37+
? multiply(
38+
current,
39+
interpolate(next, {
40+
inputRange: [0, 1],
41+
outputRange: [1, 0],
42+
})
43+
)
44+
: current;
45+
46+
return {
47+
leftButtonStyle: { opacity: progress },
48+
titleStyle: { opacity: progress },
49+
};
50+
},
51+
};
52+
53+
export default class HeaderAnimated<T extends Route> extends React.Component<
54+
Props<T>
55+
> {
56+
static defaultProps = {
57+
preset: FadePreset,
58+
};
59+
60+
render() {
61+
const { preset, scenes, layout, onGoBack } = this.props;
62+
63+
return (
64+
<HeaderSheet>
65+
<View style={styles.container}>
66+
{scenes.map((scene, i, self) => {
67+
const previous = self[i - 1];
68+
const next = self[i + 1];
69+
70+
return (
71+
<HeaderAnimatedItem
72+
key={scene.route.key}
73+
preset={preset}
74+
layout={layout}
75+
scene={scene}
76+
previous={previous}
77+
next={next}
78+
onGoBack={() => onGoBack({ route: scene.route })}
79+
/>
80+
);
81+
})}
82+
</View>
83+
</HeaderSheet>
84+
);
85+
}
86+
}
87+
88+
const styles = StyleSheet.create({
89+
container: {
90+
height: Platform.OS === 'ios' ? 44 : 56,
91+
marginTop: Platform.OS === 'ios' ? 20 : StatusBar.currentHeight,
92+
},
93+
});
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as React from 'react';
2+
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
3+
import Animated from 'react-native-reanimated';
4+
import HeaderTitle from './HeaderTitle';
5+
import { Route, Layout } from '../Stack';
6+
import HeaderBackButton from './HeaderBackButton';
7+
import memoize from '../../utils/memoize';
8+
9+
export type InterpolationProps = {
10+
current: Animated.Node<number>;
11+
next?: Animated.Node<number>;
12+
layout: Layout;
13+
};
14+
15+
export type StyleInterpolator = (
16+
props: InterpolationProps
17+
) => {
18+
leftButtonStyle: any;
19+
titleStyle: any;
20+
};
21+
22+
export type HeaderAnimationPreset = {
23+
styleInterpolator: StyleInterpolator;
24+
};
25+
26+
export type Scene<T extends Route> = {
27+
title: string;
28+
route: T;
29+
progress: Animated.Node<number>;
30+
};
31+
32+
type Props<T extends Route> = {
33+
layout: Layout;
34+
onGoBack: () => void;
35+
preset: HeaderAnimationPreset;
36+
scene: Scene<T>;
37+
previous?: Scene<T>;
38+
next?: Scene<T>;
39+
style?: StyleProp<ViewStyle>;
40+
};
41+
42+
export default class HeaderAnimatedItem<
43+
T extends Route
44+
> extends React.Component<Props<T>> {
45+
private getInterpolatedStyle = memoize(
46+
(
47+
styleInterpolator: StyleInterpolator,
48+
layout: Layout,
49+
current: Animated.Node<number>,
50+
next?: Animated.Node<number>
51+
) => styleInterpolator({ current, next, layout })
52+
);
53+
54+
render() {
55+
const {
56+
scene,
57+
previous,
58+
next,
59+
preset,
60+
layout,
61+
onGoBack,
62+
style,
63+
} = this.props;
64+
65+
const { titleStyle, leftButtonStyle } = this.getInterpolatedStyle(
66+
preset.styleInterpolator,
67+
layout,
68+
scene.progress,
69+
next ? next.progress : undefined
70+
);
71+
72+
return (
73+
<View style={[styles.content, style]}>
74+
{previous ? (
75+
<Animated.View style={[styles.left, leftButtonStyle]}>
76+
<HeaderBackButton onPress={onGoBack} title={previous.title} />
77+
</Animated.View>
78+
) : null}
79+
<HeaderTitle style={[previous ? styles.title : null, titleStyle]}>
80+
{scene.title}
81+
</HeaderTitle>
82+
</View>
83+
);
84+
}
85+
}
86+
87+
const styles = StyleSheet.create({
88+
content: {
89+
...StyleSheet.absoluteFillObject,
90+
paddingHorizontal: 4,
91+
flexDirection: 'row',
92+
alignItems: 'center',
93+
},
94+
left: {
95+
position: 'absolute',
96+
left: 0,
97+
top: 0,
98+
bottom: 0,
99+
justifyContent: 'center',
100+
},
101+
title: {
102+
marginHorizontal: 48,
103+
},
104+
});

src/components/Header/HeaderBackButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class HeaderBackButton extends React.Component<Props, State> {
4141
ios: '#037aff',
4242
web: '#5f6368',
4343
}),
44+
backTitleVisible: Platform.OS === 'ios',
4445
truncatedTitle: 'Back',
4546
};
4647

@@ -163,7 +164,7 @@ const styles = StyleSheet.create({
163164
width: 36,
164165
margin: 6,
165166
alignItems: 'center',
166-
justifyContent: 'center'
167+
justifyContent: 'center',
167168
},
168169
container: {
169170
alignItems: 'center',

src/components/Header/HeaderSimple.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import * as React from 'react';
2-
import { View, StyleSheet, Platform, StatusBar } from 'react-native';
2+
import {
3+
View,
4+
StyleSheet,
5+
Platform,
6+
StatusBar,
7+
StyleProp,
8+
ViewStyle,
9+
} from 'react-native';
310
import HeaderBackButton from './HeaderBackButton';
411
import HeaderTitle from './HeaderTitle';
512
import HeaderSheet from './HeaderSheet';
613

714
type Props = {
815
title: string;
916
onGoBack?: () => void;
17+
style?: StyleProp<ViewStyle>;
1018
};
1119

12-
export default function HeaderAndroid({ title, onGoBack }: Props) {
20+
export default function HeaderAndroid({ title, onGoBack, style }: Props) {
1321
return (
14-
<HeaderSheet>
22+
<HeaderSheet style={style}>
1523
<View style={styles.content}>
1624
{onGoBack ? <HeaderBackButton onPress={onGoBack} /> : null}
1725
<HeaderTitle>{title}</HeaderTitle>

src/components/Header/HeaderTitle.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import * as React from 'react';
2-
import { Text, StyleSheet, Platform, StyleProp, TextStyle } from 'react-native';
2+
import { StyleSheet, Platform } from 'react-native';
3+
import Animated from 'react-native-reanimated';
34

45
type Props = {
56
children: string;
6-
style?: StyleProp<TextStyle>;
7+
style?: React.ComponentProps<typeof Animated.Text>['style'];
78
};
89

910
export default function HeaderTitle({ children, style }: Props) {
10-
return <Text style={[styles.title, style]}>{children}</Text>;
11+
return (
12+
<Animated.Text style={[styles.title, style]}>{children}</Animated.Text>
13+
);
1114
}
1215

1316
const styles = StyleSheet.create({
@@ -16,6 +19,7 @@ const styles = StyleSheet.create({
1619
marginHorizontal: 12,
1720
...Platform.select({
1821
ios: {
22+
textAlign: 'center',
1923
fontSize: 17,
2024
fontWeight: '600',
2125
color: 'rgba(0, 0, 0, .9)',

0 commit comments

Comments
 (0)