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

Commit

Permalink
Add a simple animated header
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed May 21, 2019
1 parent 279ef91 commit 5d25053
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 54 deletions.
4 changes: 2 additions & 2 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -387,15 +387,15 @@ export default class Card extends React.Component<Props> {

const styles = StyleSheet.create({
container: {
flex: 1
flex: 1,
},
card: {
...StyleSheet.absoluteFillObject,
shadowOffset: { width: -1, height: 1 },
shadowRadius: 5,
shadowColor: '#000',
backgroundColor: 'white',
elevation: 2
elevation: 2,
},
overlay: {
...StyleSheet.absoluteFillObject,
Expand Down
93 changes: 93 additions & 0 deletions src/components/Header/HeaderAnimated.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import * as React from 'react';
import {
View,
StyleSheet,
Platform,
StatusBar,
StyleProp,
ViewStyle,
} from 'react-native';
import Animated from 'react-native-reanimated';
import HeaderSheet from './HeaderSheet';
import { Route, Layout } from '../Stack';
import HeaderAnimatedItem, {
HeaderAnimationPreset,
InterpolationProps,
} from './HeaderAnimatedItem';

type Scene<T extends Route> = {
title: string;
route: T;
progress: Animated.Node<number>;
};

type Props<T extends Route> = {
layout: Layout;
onGoBack: (props: { route: T }) => void;
preset: HeaderAnimationPreset;
scenes: Scene<T>[];
style?: StyleProp<ViewStyle>;
};

const { interpolate, multiply } = Animated;

const FadePreset: HeaderAnimationPreset = {
styleInterpolator: ({ current, next }: InterpolationProps) => {
const progress = next
? multiply(
current,
interpolate(next, {
inputRange: [0, 1],
outputRange: [1, 0],
})
)
: current;

return {
leftButtonStyle: { opacity: progress },
titleStyle: { opacity: progress },
};
},
};

export default class HeaderAnimated<T extends Route> extends React.Component<
Props<T>
> {
static defaultProps = {
preset: FadePreset,
};

render() {
const { preset, scenes, layout, onGoBack } = this.props;

return (
<HeaderSheet>
<View style={styles.container}>
{scenes.map((scene, i, self) => {
const previous = self[i - 1];
const next = self[i + 1];

return (
<HeaderAnimatedItem
key={scene.route.key}
preset={preset}
layout={layout}
scene={scene}
previous={previous}
next={next}
onGoBack={() => onGoBack({ route: scene.route })}
/>
);
})}
</View>
</HeaderSheet>
);
}
}

const styles = StyleSheet.create({
container: {
height: Platform.OS === 'ios' ? 44 : 56,
marginTop: Platform.OS === 'ios' ? 20 : StatusBar.currentHeight,
},
});
104 changes: 104 additions & 0 deletions src/components/Header/HeaderAnimatedItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import Animated from 'react-native-reanimated';
import HeaderTitle from './HeaderTitle';
import { Route, Layout } from '../Stack';
import HeaderBackButton from './HeaderBackButton';
import memoize from '../../utils/memoize';

export type InterpolationProps = {
current: Animated.Node<number>;
next?: Animated.Node<number>;
layout: Layout;
};

export type StyleInterpolator = (
props: InterpolationProps
) => {
leftButtonStyle: any;
titleStyle: any;
};

export type HeaderAnimationPreset = {
styleInterpolator: StyleInterpolator;
};

export type Scene<T extends Route> = {
title: string;
route: T;
progress: Animated.Node<number>;
};

type Props<T extends Route> = {
layout: Layout;
onGoBack: () => void;
preset: HeaderAnimationPreset;
scene: Scene<T>;
previous?: Scene<T>;
next?: Scene<T>;
style?: StyleProp<ViewStyle>;
};

export default class HeaderAnimatedItem<
T extends Route
> extends React.Component<Props<T>> {
private getInterpolatedStyle = memoize(
(
styleInterpolator: StyleInterpolator,
layout: Layout,
current: Animated.Node<number>,
next?: Animated.Node<number>
) => styleInterpolator({ current, next, layout })
);

render() {
const {
scene,
previous,
next,
preset,
layout,
onGoBack,
style,
} = this.props;

const { titleStyle, leftButtonStyle } = this.getInterpolatedStyle(
preset.styleInterpolator,
layout,
scene.progress,
next ? next.progress : undefined
);

return (
<View style={[styles.content, style]}>
{previous ? (
<Animated.View style={[styles.left, leftButtonStyle]}>
<HeaderBackButton onPress={onGoBack} title={previous.title} />
</Animated.View>
) : null}
<HeaderTitle style={[previous ? styles.title : null, titleStyle]}>
{scene.title}
</HeaderTitle>
</View>
);
}
}

const styles = StyleSheet.create({
content: {
...StyleSheet.absoluteFillObject,
paddingHorizontal: 4,
flexDirection: 'row',
alignItems: 'center',
},
left: {
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
justifyContent: 'center',
},
title: {
marginHorizontal: 48,
},
});
3 changes: 2 additions & 1 deletion src/components/Header/HeaderBackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class HeaderBackButton extends React.Component<Props, State> {
ios: '#037aff',
web: '#5f6368',
}),
backTitleVisible: Platform.OS === 'ios',
truncatedTitle: 'Back',
};

Expand Down Expand Up @@ -163,7 +164,7 @@ const styles = StyleSheet.create({
width: 36,
margin: 6,
alignItems: 'center',
justifyContent: 'center'
justifyContent: 'center',
},
container: {
alignItems: 'center',
Expand Down
14 changes: 11 additions & 3 deletions src/components/Header/HeaderSimple.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import * as React from 'react';
import { View, StyleSheet, Platform, StatusBar } from 'react-native';
import {
View,
StyleSheet,
Platform,
StatusBar,
StyleProp,
ViewStyle,
} from 'react-native';
import HeaderBackButton from './HeaderBackButton';
import HeaderTitle from './HeaderTitle';
import HeaderSheet from './HeaderSheet';

type Props = {
title: string;
onGoBack?: () => void;
style?: StyleProp<ViewStyle>;
};

export default function HeaderAndroid({ title, onGoBack }: Props) {
export default function HeaderAndroid({ title, onGoBack, style }: Props) {
return (
<HeaderSheet>
<HeaderSheet style={style}>
<View style={styles.content}>
{onGoBack ? <HeaderBackButton onPress={onGoBack} /> : null}
<HeaderTitle>{title}</HeaderTitle>
Expand Down
10 changes: 7 additions & 3 deletions src/components/Header/HeaderTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import * as React from 'react';
import { Text, StyleSheet, Platform, StyleProp, TextStyle } from 'react-native';
import { StyleSheet, Platform } from 'react-native';
import Animated from 'react-native-reanimated';

type Props = {
children: string;
style?: StyleProp<TextStyle>;
style?: React.ComponentProps<typeof Animated.Text>['style'];
};

export default function HeaderTitle({ children, style }: Props) {
return <Text style={[styles.title, style]}>{children}</Text>;
return (
<Animated.Text style={[styles.title, style]}>{children}</Animated.Text>
);
}

const styles = StyleSheet.create({
Expand All @@ -16,6 +19,7 @@ const styles = StyleSheet.create({
marginHorizontal: 12,
...Platform.select({
ios: {
textAlign: 'center',
fontSize: 17,
fontWeight: '600',
color: 'rgba(0, 0, 0, .9)',
Expand Down
Loading

0 comments on commit 5d25053

Please sign in to comment.