react-native-image-picker自定义过渡动画:使用react-native-reanimated 3实现
·
react-native-image-picker自定义过渡动画:使用react-native-reanimated 3实现
你还在为React Native应用中的图片选择交互生硬而烦恼吗?本文将展示如何通过react-native-reanimated 3为react-native-image-picker实现流畅的过渡动画,解决原生模块调用时界面切换突兀的问题。读完本文你将掌握:动画状态管理、原生模块与JS桥接通信、复杂动画曲线实现。
核心实现思路
react-native-image-picker的原生模块调用会导致界面无过渡切换,需通过以下方案解决:
- 状态拦截:通过包装
launchCamera和launchImageLibrary方法,在调用原生模块前后插入动画控制 - 动画驱动:使用react-native-reanimated 3创建透明度、缩放、位移组合动画
- 通信桥接:通过Promise链式调用确保动画与原生模块生命周期同步
核心原理流程图:
实现步骤
1. 安装依赖
npm install react-native-reanimated@3.x react-native-gesture-handler
2. 创建动画包装组件
// src/components/AnimatedImagePicker.tsx
import React, { useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import Animated, {
useAnimatedStyle,
withTiming,
Easing,
runOnJS
} from 'react-native-reanimated';
import { launchCamera, launchImageLibrary } from 'react-native-image-picker';
import type { CameraOptions, ImageLibraryOptions, ImagePickerResponse } from '../types';
// 动画配置常量
const ANIMATION_CONFIG = {
duration: 350,
easing: Easing.out(Easing.back(1.2)),
};
export const AnimatedImagePicker = () => {
const animationState = useRef({
isAnimating: false,
progress: 0,
}).current;
// 退场动画样式 (原生模块启动前)
const exitAnimationStyle = useAnimatedStyle(() => ({
opacity: withTiming(animationState.progress, ANIMATION_CONFIG),
transform: [
{ scale: withTiming(animationState.progress * 0.9 + 0.1, ANIMATION_CONFIG) },
],
}));
// 入场动画样式 (原生模块返回后)
const enterAnimationStyle = useAnimatedStyle(() => ({
opacity: withTiming(animationState.progress, ANIMATION_CONFIG),
transform: [
{ translateY: withTiming((1 - animationState.progress) * 50, ANIMATION_CONFIG) },
],
}));
// 包装相机启动方法
const animatedLaunchCamera = async (
options: CameraOptions
): Promise<ImagePickerResponse> => {
if (animationState.isAnimating) return { didCancel: true };
animationState.isAnimating = true;
// 执行退场动画
animationState.progress = 0;
return new Promise(resolve => {
// 等待动画完成后调用原生模块
setTimeout(async () => {
const result = await launchCamera(options);
// 执行入场动画
animationState.progress = 1;
setTimeout(() => {
animationState.isAnimating = false;
runOnJS(resolve)(result);
}, ANIMATION_CONFIG.duration);
}, ANIMATION_CONFIG.duration);
});
};
return (
<Animated.View style={[styles.container, animationState.progress < 0.5 ? exitAnimationStyle : enterAnimationStyle]}>
{/* 你的相机/相册触发按钮 */}
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
关键代码解析
动画状态管理
通过animationState对象维护动画生命周期:
const animationState = useRef({
isAnimating: false, // 防止并发动画
progress: 0, // 0-1动画进度值
}).current;
方法拦截实现
在src/index.ts中,原方法直接调用平台相关实现:
export function launchCamera(options: CameraOptions, callback?: Callback) {
return Platform.OS === 'web'
? webCamera(options, callback)
: nativeCamera(options, callback);
}
我们通过高阶函数模式包装原生调用:
// 高阶动画包装函数
const withAnimation = <T extends (...args: any[]) => Promise<any>>(
fn: T,
animationType: 'enter' | 'exit' = 'exit'
): T => {
return ((...args: Parameters<T>) => {
// 动画逻辑实现...
return fn(...args);
}) as T;
};
// 使用方式
const animatedLaunchImageLibrary = withAnimation(launchImageLibrary);
动画曲线优化
利用react-native-reanimated 3的Easing函数创建自然动画曲线:
// 弹性退场效果
Easing.out(Easing.back(1.2)) // 回弹效果
// 阻尼入场效果
Easing.bezier(0.25, 0.1, 0.25, 1.0) // 平滑减速
完整集成示例
以下是包含图片选择和预览的完整页面组件,结合了本文实现的动画效果:
// src/screens/ImageCaptureScreen.tsx
import React, { useState } from 'react';
import { View, Button, Image, StyleSheet } from 'react-native';
import { AnimatedImagePicker } from '../components/AnimatedImagePicker';
import type { Asset } from '../types';
export const ImageCaptureScreen = () => {
const [selectedAsset, setSelectedAsset] = useState<Asset | null>(null);
const { animatedLaunchCamera, animatedLaunchImageLibrary } = AnimatedImagePicker();
const handleCameraPress = async () => {
const result = await animatedLaunchCamera({
mediaType: 'photo',
quality: 0.8,
presentationStyle: 'pageSheet', // 配合iOS 13+的模态样式
});
if (!result.didCancel && result.assets?.[0]) {
setSelectedAsset(result.assets[0]);
}
};
return (
<View style={styles.screen}>
{selectedAsset && (
<Animated.Image
source={{ uri: selectedAsset.uri }}
style={styles.previewImage}
/>
)}
<View style={styles.buttonsContainer}>
<Button title="拍摄照片" onPress={handleCameraPress} />
<Button
title="从相册选择"
onPress={() => animatedLaunchImageLibrary({ mediaType: 'photo' })}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
padding: 20,
},
previewImage: {
width: '100%',
height: 300,
borderRadius: 12,
marginBottom: 20,
},
buttonsContainer: {
flexDirection: 'row',
gap: 10,
},
});
性能优化与注意事项
-
动画中断处理:在
AnimatedImagePicker组件中添加手势取消逻辑,防止动画过程中用户重复操作 -
内存管理:对于频繁调用的场景,使用
useCallback缓存动画函数:
const animatedLaunchCamera = useCallback(async (options) => {
// 实现逻辑
}, []);
- 平台适配:根据src/platforms/native.ts中的平台差异,调整动画参数:
const PLATFORM_ANIMATION_CONFIG = Platform.select({
ios: { duration: 300, easing: Easing.out(Easing.back(1.1)) },
android: { duration: 250, easing: Easing.bezier(0.34, 1.56, 0.64, 1) },
});
- 错误边界:包装原生模块调用防止动画状态异常:
try {
const result = await launchCamera(options);
} catch (error) {
// 重置动画状态
animationState.isAnimating = false;
animationState.progress = 1;
}
总结
通过本文方法,我们成功为react-native-image-picker添加了自定义过渡动画,主要关键点:
- 使用状态拦截模式包装原生方法,实现动画与原生模块生命周期同步
- 结合多种动画参数(透明度、缩放、位移)创建丰富视觉效果
- 通过Easing函数和动画曲线优化提升交互质感
完整实现代码可参考项目example/src/components/DemoButton.tsx中的交互逻辑,建议配合react-native-reanimated的useAnimatedScrollHandler实现更复杂的滚动关联动画。
掌握这些技巧后,你可以进一步扩展实现:图片选择后的裁剪动画、多图选择的瀑布流加载动画、视频录制的进度动画等高级交互效果。
更多推荐
所有评论(0)