欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从0到1打造一款react-native App(三)Camera

程序员文章站 2022-04-25 20:28:19
...

关联文章


从0到1打造一款react-native App(一)环境配置

从0到1打造一款react-native App(二)Navigation+Redux

项目地址:https://github.com/jiwenjiang/react-native-nfc

拍照(摄像)需求

拍照的主要需求是在拍照后,不将照片在系统相册中显示出来,android拍照后会默认存储在DCIM文件夹当中,而这次主要需要做的就是把照片放在自定义的文件夹当中。

react-native-camera

拍照的第三方包有很多,比如react-native-image-picker,这个调用的是系统相机,用法比较简单,但是拓展性较差,不管是这次项目主要的需求(拍照后不在系统相册显示),还是本身拍照时的一些定制化的需求,类似微信拍照那种,都不容易实现,因此选择了react-native-camera

最新版的react-native-camera(v 1.1.x)已经支持了人脸识别,文字识别等功能,还是很强大的,这些功能可能日后都会用得到,不过因为一些版本和平台的原因之后会换成expo的camera,这里暂时还是介绍rn的camera(v 0.7)。

组件二次封装:

import React, { Component } from 'react';
import {
    Dimensions,
    StyleSheet,
    Button,
    Text,
    ImageBackground,
    View,
    TouchableOpacity
} from 'react-native';
import Camera from 'react-native-camera';
import Icon from 'react-native-vector-icons/MaterialIcons';
import { deleteFile, mkdir, readPath } from '../../service/utils/fileOperations';
import RNFS from 'react-native-fs';
import moment from 'moment/moment';

class RNCamera extends Component {
    constructor(props) {
        super(props);
        this.state = {
            hidden: false,
            currentImage: null
        };
    }

    async takePicture() {
        const options = {};
        const { path: currentImage } = await this.camera.capture({ metadata: options });
        this.setState({ currentImage });
    }

    back() {
        this.setState({ currentImage: null, hidden: true });
    }

    async check() {
        const [date, unixTime] = [moment().format('YYYY/MM/DD'), moment().unix()];
        const dir = `${RNFS.DocumentDirectoryPath}/photo/${date}`;
        await mkdir(dir);
        const url = `${dir}/${unixTime}.jpg`;
        await RNFS.moveFile(this.state.currentImage, url);
        console.log(await readPath(dir));
        this.setState({ currentImage: null });
    }

    cancel() {
        deleteFile(this.state.currentImage);
        this.setState({ currentImage: null });
    }


    render() {
        const { currentImage, hidden } = this.state;
        return (
                <View style={[styles.container, hidden && styles.hidden]}>
                    {currentImage ? <ImageBackground style={styles.photo} source={{ uri: currentImage }}>
                            <TouchableOpacity style={styles.capture} onPress={() => this.cancel()}>
                                <Icon name="close" size={30}/>
                            </TouchableOpacity >
                            <TouchableOpacity style={styles.capture} onPress={() => this.check()}>
                                <Icon name="check" size={30}/>
                            </TouchableOpacity >
                            </ImageBackground >
                            : <Camera ref={(cam) => {
                                this.camera = cam;
                            }}
                                      style={styles.preview}
                                      aspect={Camera.constants.Aspect.fill}
                                      captureTarget={Camera.constants.CaptureTarget.temp}
                            >
                            <TouchableOpacity style={styles.capture} onPress={() => this.back()}>
                                <Icon name="expand-more" size={30}/>
                            </TouchableOpacity >
                            <TouchableOpacity style={styles.capture} onPress={() => this.takePicture()}>
                                <Icon name="camera-alt" size={30}/>
                            </TouchableOpacity >
                            </Camera >
                    }
                </View >
        );
    }
}

const styles = StyleSheet.create(
        {
            container: {
                flex: 1,
                flexDirection: 'row'
            },
            preview: {
                flex: 1,
                justifyContent: 'center',
                flexDirection: 'row',
                alignItems: 'flex-end'
            },
            capture: {
                flex: 0,
                backgroundColor: 'rgba(255, 255, 255, 0.3)',
                borderRadius: 25,
                margin: 20,
                marginBottom: 30,
                width: 50,
                height: 50,
                alignItems: 'center',
                justifyContent: 'center',
                zIndex: 1
            },
            photo: {
                flex: 1,
                justifyContent: 'center',
                flexDirection: 'row',
                alignItems: 'flex-end'
            },
            hidden: {
                display: 'none'
            }
        }
);

export default RNCamera;

没有对react-native-camera做过多的配置,需要注意的配置是captureTarget属性。在v0.7版本的camera当中,captureTarget的可选配置项有4种。

  • Camera.constants.CaptureTarget.cameraRoll(默认,存储在系统相册中)
  • Camera.constants.CaptureTarget.disk(存储在磁盘中,这是官方推荐的存储方式,会提升拍照的响应速度)
  • Camera.constants.CaptureTarget.temp (存储在临时文件夹,当前项目选择方案)
  • Camera.constants.CaptureTarget.memory (以base64的形式存储在内存当中,这个选项在之后的版本已经被废弃了,不过0.7版本还是可以用的)

    实现基本思路是,通过外层调用来控制整个组件的样式值,来管理组件的显示与隐藏,即组件state的hidden属性。当组件被成功调用显示时,组件主要分为两块,拍照和预览。给定一个拍照照片的路径值,即组件state的currentImage,如果当前组件存在一个照片的存储路径,就显示预览界面,如不存在就显示拍照界面。而currentImage的值通过拍照成功的Promise或者取消的状态去控制创建与删除。

拍照时去创建currentImage

async takePicture() {
        const options = {};
        const { path: currentImage } = await this.camera.capture({ metadata: options });
        this.setState({ currentImage });
    }

隐藏组建,返回调用界面

 back() {
        this.setState({ currentImage: null, hidden: true });
    }

拍照完成后预览照片及确认存储

async check() {
        const [date, unixTime] = [moment().format('YYYY/MM/DD'), moment().unix()];
        const dir = `${RNFS.DocumentDirectoryPath}/photo/${date}`;
        await mkdir(dir);
        const url = `${dir}/${unixTime}.jpg`;
        await RNFS.moveFile(this.state.currentImage, url);
        console.log(await readPath(dir));
        this.setState({ currentImage: null });
    }

存储这里用到了react-native-fs,这个第三方包就不过多介绍了,都是一些基础的文件操作,比较好理解。通过在文件路径下新建photo/xxxx-xx-xx的文件夹,确保每天拍摄的照片存放在当日的文件夹,方便后续的文件预览时的筛选。在照片拍摄完毕后,react-native-camera会将拍摄的照片存放至临时文件夹,而这里需要做的就是将临时文件夹的照片移动至我们的目标文件夹,这里顺便说一下,文件move操作的性能是优于read+write的,这里切记用move。关于android文件存储这里推荐一篇介绍的比较详细的文章https://juejin.im/post/58b557de128fe10065e93cc8

拍照完成后预览照片及放弃存储

cancel() {
        deleteFile(this.state.currentImage);
        this.setState({ currentImage: null });
    }

操作预览:
从0到1打造一款react-native App(三)Camera
从0到1打造一款react-native App(三)Camera

照片回显

<View style={styles.container}>
      <Image style={styles.photo}
           source={{ uri: `file://${files[0].path}` }} //显示第一张照片
      />              
</View >

在照片回显时,检测文件夹,读取照片

const mkdir = async (url) => {
    const dirExists = await RNFS.exists(url);
    if (dirExists) {
        return new Promise(resolve => resolve(dirExists));
    }
    await RNFS.mkdir(url);
    return new Promise(resolve => resolve(url));
};
async function storageFile() {
    const date = moment().format('YYYY/MM/DD');
    const url = `${RNFS.DocumentDirectoryPath}/photo/${date}`;
    await mkdir(url);
    const files = await readPath(url);
    return files;
}

二维码扫描

react-native-camera支持对各种条形码的扫描识别,主要的属性有两个
barCodeTypes={[Camera.constants.BarCodeType.qr]} //扫码的类型
onBarCodeRead={this.props.onScanResultReceived} //扫码成功后的回调

项目这里直接把https://www.jianshu.com/p/347ccf787d62这篇文章中二次封装好的一个二维码扫描的组件复制了过来。主要是视图层的二次封装,有兴趣的同学也可以自己封装。

之后会把react-native-camera替换成expo中的camera,换完之后会继续在这篇camera的文章中更新,也欢迎正在学习的同学一起交流~