2

关联文章


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

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

项目地址:https://github.com/jiwenjiang...

拍照(摄像)需求

拍照的主要需求是在拍照后,不将照片在系统相册中显示出来,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/58b557...

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

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

操作预览:

图片描述

图片描述

照片回显

<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/347... 这篇文章中二次封装好的一个二维码扫描的组件复制了过来。主要是视图层的二次封装,有兴趣的同学也可以自己封装。

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


j_bleach
2.5k 声望70 粉丝

1 >> 1