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

Commit

Permalink
Initial work on UIKit style animation for header
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed May 21, 2019
1 parent 5d25053 commit 9db82ec
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 34 deletions.
78 changes: 65 additions & 13 deletions src/components/Header/HeaderAnimated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,75 @@ type Props<T extends Route> = {
style?: StyleProp<ViewStyle>;
};

const { interpolate, multiply } = Animated;
const { interpolate, add } = Animated;

const UIKitPreset: HeaderAnimationPreset = {
styleInterpolator: ({ current, next, layout }: InterpolationProps) => {
/**
* NOTE: this offset calculation is an approximation that gives us
* decent results in many cases, but it is ultimately a poor substitute
* for text measurement. See the comment on title for more information.
*
* - 70 is the width of the left button area.
* - 25 is the width of the left button icon (to account for label offset)
*/
const buttonAreaSize = 70;
const buttonIconSize = 25;
const titleOffset = layout.width / 2 - buttonAreaSize + buttonIconSize;
const backTitleOffset = layout.width / 2 - buttonAreaSize - buttonIconSize;

const progress = add(current, next ? next : 0);

return {
leftButtonStyle: {
opacity: interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [0, 1, 0],
}),
},
backTitleStyle: {
opacity: interpolate(progress, {
inputRange: [0.7, 1, 1.3],
outputRange: [0, 1, 0],
}),
transform: [
{
translateX: interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [backTitleOffset, 0, -backTitleOffset],
}),
},
],
},
titleStyle: {
opacity: interpolate(progress, {
inputRange: [0.5, 1, 1.7],
outputRange: [0, 1, 0],
}),
transform: [
{
translateX: interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [titleOffset, 0, -titleOffset],
}),
},
],
},
};
},
};

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

return {
leftButtonStyle: { opacity: progress },
titleStyle: { opacity: progress },
leftButtonStyle: { opacity },
titleStyle: { opacity },
};
},
};
Expand All @@ -54,7 +106,7 @@ export default class HeaderAnimated<T extends Route> extends React.Component<
Props<T>
> {
static defaultProps = {
preset: FadePreset,
preset: Platform.OS === 'ios' ? UIKitPreset : FadePreset,
};

render() {
Expand Down
17 changes: 13 additions & 4 deletions src/components/Header/HeaderAnimatedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export type InterpolationProps = {
export type StyleInterpolator = (
props: InterpolationProps
) => {
leftButtonStyle: any;
titleStyle: any;
backTitleStyle?: any;
leftButtonStyle?: any;
titleStyle?: any;
};

export type HeaderAnimationPreset = {
Expand Down Expand Up @@ -62,7 +63,11 @@ export default class HeaderAnimatedItem<
style,
} = this.props;

const { titleStyle, leftButtonStyle } = this.getInterpolatedStyle(
const {
titleStyle,
leftButtonStyle,
backTitleStyle,
} = this.getInterpolatedStyle(
preset.styleInterpolator,
layout,
scene.progress,
Expand All @@ -73,7 +78,11 @@ export default class HeaderAnimatedItem<
<View style={[styles.content, style]}>
{previous ? (
<Animated.View style={[styles.left, leftButtonStyle]}>
<HeaderBackButton onPress={onGoBack} title={previous.title} />
<HeaderBackButton
onPress={onGoBack}
title={previous.title}
titleStyle={backTitleStyle}
/>
</Animated.View>
) : null}
<HeaderTitle style={[previous ? styles.title : null, titleStyle]}>
Expand Down
72 changes: 55 additions & 17 deletions src/components/Header/HeaderBackButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import * as React from 'react';
import {
I18nManager,
Image,
Text,
View,
Platform,
StyleSheet,
LayoutChangeEvent,
StyleProp,
TextStyle,
Text,
MaskedViewIOS,
} from 'react-native';

import Animated from 'react-native-reanimated';
import TouchableItem from '../TouchableItem';

type Props = {
Expand All @@ -26,12 +25,12 @@ type Props = {
truncatedTitle?: string | null;
backTitleVisible?: boolean;
allowFontScaling?: boolean;
titleStyle?: StyleProp<TextStyle>;
titleStyle?: React.ComponentProps<typeof Text>['style'];
width?: number;
};

type State = {
initialTextWidth?: number;
initialTitleWidth?: number;
};

class HeaderBackButton extends React.Component<Props, State> {
Expand All @@ -48,11 +47,12 @@ class HeaderBackButton extends React.Component<Props, State> {
state: State = {};

private handleTextLayout = (e: LayoutChangeEvent) => {
if (this.state.initialTextWidth) {
if (this.state.initialTitleWidth) {
return;
}

this.setState({
initialTextWidth: e.nativeEvent.layout.x + e.nativeEvent.layout.width,
initialTitleWidth: e.nativeEvent.layout.x + e.nativeEvent.layout.width,
});
};

Expand Down Expand Up @@ -83,7 +83,7 @@ class HeaderBackButton extends React.Component<Props, State> {
private getTitleText = () => {
const { width, title, truncatedTitle } = this.props;

let { initialTextWidth } = this.state;
let { initialTitleWidth: initialTextWidth } = this.state;

if (title === null) {
return null;
Expand All @@ -103,22 +103,41 @@ class HeaderBackButton extends React.Component<Props, State> {
titleStyle,
tintColor,
} = this.props;
const { initialTitleWidth: titleWidth } = this.state;

let backTitleText = this.getTitleText();

if (!backTitleVisible || backTitleText === null) {
return null;
}

return (
<Text
accessible={false}
onLayout={this.handleTextLayout}
style={[styles.title, !!tintColor && { color: tintColor }, titleStyle]}
numberOfLines={1}
allowFontScaling={!!allowFontScaling}
<MaskedViewIOS
maskElement={
<View style={styles.iconMaskContainer}>
<Image
source={require('../../assets/back-icon-mask.png')}
style={styles.iconMask}
/>
<View style={styles.iconMaskFillerRect} />
</View>
}
>
{this.getTitleText()}
</Text>
<Animated.Text
accessible={false}
onLayout={this.handleTextLayout}
style={[
styles.title,
tintColor ? { color: tintColor } : null,
titleWidth ? { paddingRight: titleWidth } : null,
titleStyle,
]}
numberOfLines={1}
allowFontScaling={!!allowFontScaling}
>
{this.getTitleText()}
</Animated.Text>
</MaskedViewIOS>
);
}

Expand Down Expand Up @@ -173,6 +192,7 @@ const styles = StyleSheet.create({
},
title: {
fontSize: 17,
letterSpacing: 0.25,
paddingRight: 10,
},
icon: Platform.select({
Expand Down Expand Up @@ -200,6 +220,24 @@ const styles = StyleSheet.create({
marginRight: 6,
}
: {},
iconMaskContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
iconMaskFillerRect: {
flex: 1,
backgroundColor: '#000',
},
iconMask: {
height: 21,
width: 13,
marginLeft: -14.5,
marginVertical: 12,
alignSelf: 'center',
resizeMode: 'contain',
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},
});

export default HeaderBackButton;

0 comments on commit 9db82ec

Please sign in to comment.