mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-04-23 20:10:49 +08:00
feat: add a slide animation for modals on Android
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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)),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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={[
|
||||
|
||||
Reference in New Issue
Block a user