feat: add a slide animation for modals on Android

This commit is contained in:
Satyajit Sahoo
2020-11-16 18:05:05 +01:00
parent 422dfc55dd
commit 6f326cf0c5
6 changed files with 106 additions and 24 deletions

View File

@@ -5,7 +5,6 @@ import type { ParamListBase } from '@react-navigation/native';
import {
createStackNavigator,
StackScreenProps,
StackNavigationOptions,
} from '@react-navigation/stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
@@ -71,13 +70,11 @@ const AlbumsScreen = ({ navigation }: StackScreenProps<ModalStackParams>) => {
);
};
const ModalPresentationStack = createStackNavigator<ModalStackParams>();
const ModalStack = createStackNavigator<ModalStackParams>();
type Props = StackScreenProps<ParamListBase> & {
options?: StackNavigationOptions;
};
type Props = StackScreenProps<ParamListBase>;
export default function SimpleStackScreen({ navigation, options }: Props) {
export default function ModalStackScreen({ navigation }: Props) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
@@ -85,8 +82,8 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
}, [navigation]);
return (
<ModalPresentationStack.Navigator mode="modal" {...options}>
<ModalPresentationStack.Screen
<ModalStack.Navigator mode="modal">
<ModalStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
@@ -94,12 +91,12 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
})}
initialParams={{ author: 'Gandalf' }}
/>
<ModalPresentationStack.Screen
<ModalStack.Screen
name="Albums"
component={AlbumsScreen}
options={{ title: 'Albums' }}
/>
</ModalPresentationStack.Navigator>
</ModalStack.Navigator>
);
}

View File

@@ -40,7 +40,7 @@ import { restartApp } from './Restart';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import ModalStack from './Screens/ModalStack';
import StackTransparent from './Screens/StackTransparent';
import StackHeaderCustomization from './Screens/StackHeaderCustomization';
import BottomTabs from './Screens/BottomTabs';
@@ -66,9 +66,9 @@ type RootDrawerParamList = {
const SCREENS = {
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
ModalPresentationStack: {
title: 'Modal Presentation Stack',
component: ModalPresentationStack,
ModalStack: {
title: 'Modal Stack',
component: ModalStack,
},
StackTransparent: {
title: 'Transparent Stack',

View File

@@ -82,10 +82,7 @@ export function forVerticalIOS({
return {
cardStyle: {
transform: [
// Translation for the animation of the current card
{ translateY },
],
transform: [{ translateY }],
},
};
}
@@ -195,6 +192,7 @@ export function forFadeFromBottomAndroid({
current.progress.interpolate({
inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1],
extrapolate: 'clamp',
})
);
@@ -266,7 +264,7 @@ export function forRevealFromBottomAndroid({
}
/**
* Standard Android-style reveal from the bottom for Android Q.
* Standard Android-style zoom for Android 10.
*/
export function forScaleFromCenterAndroid({
current,
@@ -307,13 +305,56 @@ export function forScaleFromCenterAndroid({
);
return {
containerStyle: {
cardStyle: {
opacity,
transform: [{ scale }],
},
};
}
/**
* Standard bottom sheet slide in from the bottom for Android.
*/
export function forBottomSheetAndroid({
current,
inverted,
layouts: { screen },
closing,
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
const translateY = multiply(
current.progress.interpolate({
inputRange: [0, 1],
outputRange: [screen.height * 0.8, 0],
extrapolate: 'clamp',
}),
inverted
);
const opacity = conditional(
closing,
current.progress,
current.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp',
})
);
const overlayOpacity = current.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 0.3],
extrapolate: 'clamp',
});
return {
cardStyle: {
opacity,
transform: [{ translateY }],
},
overlayStyle: { opacity: overlayOpacity },
};
}
export function forNoAnimation(): StackCardInterpolatedStyle {
return {};
}

View File

@@ -2,10 +2,11 @@ import { Platform } from 'react-native';
import {
forHorizontalIOS,
forVerticalIOS,
forModalPresentationIOS,
forScaleFromCenterAndroid,
forRevealFromBottomAndroid,
forFadeFromBottomAndroid,
forModalPresentationIOS,
forBottomSheetAndroid,
} from './CardStyleInterpolators';
import { forFade } from './HeaderStyleInterpolators';
import {
@@ -14,6 +15,8 @@ import {
RevealFromBottomAndroidSpec,
FadeOutToBottomAndroidSpec,
FadeInFromBottomAndroidSpec,
BottomSheetSlideInSpec,
BottomSheetSlideOutSpec,
} from './TransitionSpecs';
import type { TransitionPreset } from '../types';
@@ -98,6 +101,19 @@ export const ScaleFromCenterAndroid: TransitionPreset = {
headerStyleInterpolator: forFade,
};
/**
* Standard bottom sheet slide transition for Android 10.
*/
export const BottomSheetAndroid: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
open: BottomSheetSlideInSpec,
close: BottomSheetSlideOutSpec,
},
cardStyleInterpolator: forBottomSheetAndroid,
headerStyleInterpolator: forFade,
};
/**
* Default navigation transition for the current platform.
*/
@@ -117,5 +133,5 @@ export const DefaultTransition = Platform.select({
*/
export const ModalTransition = Platform.select({
ios: ModalPresentationIOS,
default: DefaultTransition,
default: BottomSheetAndroid,
});

View File

@@ -67,3 +67,29 @@ export const ScaleFromCenterAndroidSpec: TransitionSpec = {
easing: Easing.bezier(0.35, 0.45, 0, 1),
},
};
/**
* Configuration for bottom sheet slide in animation from Material Design.
* See https://github.com/material-components/material-components-android/blob/fd3639092e1ffef9dc11bcedf79f32801d85e898/lib/java/com/google/android/material/bottomsheet/res/anim/mtrl_bottom_sheet_slide_in.xml
*/
export const BottomSheetSlideInSpec: TransitionSpec = {
animation: 'timing',
config: {
duration: 250,
// See https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
easing: (t) => Math.cos((t + 1) * Math.PI) / 2.0 + 0.5,
},
};
/**
* Configuration for bottom sheet slide out animation from Material Design.
* See https://github.com/material-components/material-components-android/blob/fd3639092e1ffef9dc11bcedf79f32801d85e898/lib/java/com/google/android/material/bottomsheet/res/anim/mtrl_bottom_sheet_slide_in.xml
*/
export const BottomSheetSlideOutSpec: TransitionSpec = {
animation: 'timing',
config: {
duration: 200,
// See https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/animation/AccelerateInterpolator.java
easing: (t) => (t === 1.0 ? 1 : Math.pow(t, 2)),
},
};

View File

@@ -541,7 +541,6 @@ export default class Card extends React.Component<Props> {
</View>
) : null}
<Animated.View
needsOffscreenAlphaCompositing={hasOpacityStyle(containerStyle)}
style={[styles.container, containerStyle, customContainerStyle]}
pointerEvents="box-none"
>
@@ -551,7 +550,10 @@ export default class Card extends React.Component<Props> {
onHandlerStateChange={this.handleGestureStateChange}
{...this.gestureActivationCriteria()}
>
<Animated.View style={[styles.container, cardStyle]}>
<Animated.View
needsOffscreenAlphaCompositing={hasOpacityStyle(cardStyle)}
style={[styles.container, cardStyle]}
>
{shadowEnabled && shadowStyle && !isTransparent ? (
<Animated.View
style={[