基于react-native-camera的简单封装

965 阅读3分钟

这是简单封装,只是为了获取从相机拍照得到的图片地址。平时我们可能会使用react-native-image-picker,但是我们只需要其中的拍照功能,同时由于这个插件的实现是使用官方的相机,也就是我们跳转的时候是跳转到官网的相机页面,这样就存在一个问题,当使用者的手机内存比较低的时候,打开相机的过程就有可能杀死我们的程序。如果相机是我们应用的一部分就不会存在这样的问题。我这次封装主要是提供跳转到相机页面,当相机有返回的时候会有对应的回调,目前有成功或者取消两种回调。

基于react-navigation 3.x封装

打开相机的方法:

  1. 将页面注册到栈式导航中;
  2. 根据注册时的路由进行跳转;
  3. 回调通过参数传递
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

三、存在的问题和想要的优化

  1. 首先操作比较多;
  2. 跟以前的方式不同;
  3. 不够自由,也就是暴露的接口不够,有很大的局限性;

以后的优化:像以前一样,只需要调用一个函数即可实现打开相机,无需注册路由;回调等方法只需要通过在配置项中填写即可。提供自定义相机界面。