这是简单封装,只是为了获取从相机拍照得到的图片地址。平时我们可能会使用react-native-image-picker,但是我们只需要其中的拍照功能,同时由于这个插件的实现是使用官方的相机,也就是我们跳转的时候是跳转到官网的相机页面,这样就存在一个问题,当使用者的手机内存比较低的时候,打开相机的过程就有可能杀死我们的程序。如果相机是我们应用的一部分就不会存在这样的问题。我这次封装主要是提供跳转到相机页面,当相机有返回的时候会有对应的回调,目前有成功或者取消两种回调。
基于react-navigation 3.x封装
打开相机的方法:
- 将页面注册到栈式导航中;
- 根据注册时的路由进行跳转;
- 回调通过参数传递
| api | 说明 |
|---|---|
| onTakePicture | 当拍完照以后的回调 |
| onCancel | 当用户在拍照过程中返回,取消,返回上一页时的回调 |
一、基本使用
// 1. 在栈中注册
// 2. 跳转页面,并添加回调
this.props.navigation.navigation("注册的路由名称", {
onTakePicture: (file) => {
// 当拍完照以后的回到,file里面包含文件相关信息
},
onCancel: () => {
// 当用户取消等操作
}
})
二、代码实现
里面使用到了Toast,这个可以自己提供。
import React, { useMemo, useEffect, useCallback } from 'react'
import { Animated, View, Text, TouchableOpacity, StyleSheet, Dimensions } from 'react-native'
import { RNCamera } from 'react-native-camera'
import { Toast } from '../../components'
let ifConfirm = false
function TakePicture({ navigation }) {
const cameraRef = React.useRef(null)
const [isTake, setTake] = React.useState(false)
const onRouteCancel = useCallback(() => {
if (!ifConfirm) {
const onCancel = navigation.getParam("onCancel")
onCancel && onCancel()
}
}, [])
useEffect(() => {
let willBlur
if (navigation) {
willBlur = navigation.addListener("willBlur", onRouteCancel)
}
return () => {
willBlur && willBlur.remove()
ifConfirm = false
}
}, [])
const takePicture = async () => {
if (cameraRef && cameraRef.current) {
const options = {
quality: 1,
width: 2 * Dimensions.get('screen').width,
base64: false,
exif: false,
pauseAfterCapture: true,
}
setTake(true)
try {
navigation.setParams({
takeData: await cameraRef.current.takePictureAsync(options)
})
} catch (err) {
console.warn(err, "在拍照的地方出现了错误")
setTake(false)
}
cameraRef.current.pausePreview()
}
}
const _onCancel = () => {
if (cameraRef && cameraRef.current) {
cameraRef.current.pausePreview();
navigation && navigation.goBack()
}
}
const _onReview = () => {
if (cameraRef && cameraRef.current) {
cameraRef.current.resumePreview();
setTake(false)
}
}
const _onConfirm = () => {
// 这里是将文件返回回去
ifConfirm = true
const onTakePicture = navigation.getParam("onTakePicture")
const takeData = navigation.getParam("takeData")
if (takeData) {
onTakePicture && onTakePicture(takeData)
navigation.goBack()
} else {
Toast.show({title: "请稍等"})
}
}
return (
<View style={styles.container}>
<Camera quote={cameraRef} />
<View style={styles.bottomTake}>
<Left
onCancel={_onCancel}
isTake={isTake}
onReview={_onReview} />
<Center isTake={isTake} onTakePicture={takePicture} />
<Right isTake={isTake} onConfirm={_onConfirm} />
</View>
</View>
)
}
function Camera({ quote }) {
return useMemo(() => (
<RNCamera
ref={quote}
style={styles.preview}
type={RNCamera.Constants.Type.back}
flashMode={RNCamera.Constants.FlashMode.off}
androidCameraPermissionOptions={{
title: '拍照权限',
message: '允许访问摄像头进行拍照',
buttonPositive: '确认',
buttonNegative: '拒绝',
}}
/>
), [])
}
function timing(state, toValue, duration) {
Animated.timing(state, {
toValue: toValue,
duration: duration
}).start()
}
function Center({ onTakePicture, isTake = false }) {
const [borderWidth] = React.useState(new Animated.Value(2))
const [opacity] = React.useState(new Animated.Value(isTake ? 0 : 1))
const _onPressIn = () => {
timing(borderWidth, 5, 100)
}
const _onPressOut = () => {
timing(borderWidth, 2, 100)
}
React.useEffect(() => {
timing(opacity, !isTake, 500)
}, [isTake])
return (
<TouchableOpacity
onPressIn={_onPressIn}
activeOpacity={1}
onPressOut={_onPressOut}
onPress={onTakePicture}
disabled={isTake}>
<Animated.View style={
StyleSheet.flatten([styles.capture, {
borderWidth,
opacity,
}])
}>
<Text style={{ fontSize: 14, color: '#fff' }}>拍照</Text>
</Animated.View>
</TouchableOpacity>
)
}
function Left({ isTake, onCancel, onReview }) {
const MyText = (onClick = () => null, text) => {
return <Text style={styles.textStyle} onPress={onClick}>{text}</Text>
}
return (
<View style={{ flex: 1, alignItems: 'center' }}>
{!isTake ? MyText(onCancel, "取消") : MyText(onReview, "重拍")}
</View>
)
}
function Right({ isTake, onConfirm }) {
return (
<TouchableOpacity
disabled={!isTake}
onPress={onConfirm}
style={{ flex: 1, alignItems: 'center', opacity: isTake ? 1 : 0 }}>
<Text style={styles.textStyle}>使用照片</Text>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: 'black',
overflow: 'hidden'
},
preview: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
},
capture: {
width: 60,
height: 60,
backgroundColor: '#1AAD19',
borderRadius: 30,
justifyContent: 'center',
alignItems: 'center',
borderColor: '#fff'
},
bottomTake: {
flex: 0,
flexDirection: 'row',
justifyContent: 'space-around',
height: 100,
overflow: 'hidden',
alignItems: 'center'
},
textStyle: {
color: '#fff',
padding: 10
}
});
TakePicture.navigationOptions = {
header: null
}
export default TakePicture
三、存在的问题和想要的优化
- 首先操作比较多;
- 跟以前的方式不同;
- 不够自由,也就是暴露的接口不够,有很大的局限性;
以后的优化:像以前一样,只需要调用一个函数即可实现打开相机,无需注册路由;回调等方法只需要通过在配置项中填写即可。提供自定义相机界面。