React Native 實踐(涵蓋豐富的移動端特有功能)

前言

本文基於 React Native 的實踐項目進行總結, 該項目基於 React Native 和 H5 在開發效率、功能性能、用戶體驗等方面的差別性,對功能模塊進行精心設計,主要基於咱們如今實際項目的業務,結合移動端特有的特性。javascript

本文圍繞 React Native 項目的環境配置、運行,React Native 介紹,項目的主要功能,React Native 開發存在的坑等多個方面進行展開。若是你尚未 React Native 開發經驗,那麼這篇文章將很好的向你展現 React Native 的各方面,包括官方文檔、生態、兼容性等等,但願你在這篇文章中找到你想要的答案。html

辛苦整理良久,還望手動點贊鼓勵~java

博客 github地址爲:github.com/fengshi123/… ,彙總了做者的全部博客,也歡迎關注及 star ~node

本項目 github 地址爲:github.com/fengshi123/…react

配套的服務端 express 項目 github 地址爲:github.com/fengshi123/…android

1、啓動項目

1.一、環境配置

在這個 React Native App 開發中,個人開發環境相關配置以下:ios

工具名稱 版本號
node.js 11.12.0
npm 6.7.0
yarn 1.17.3
Android Studio 3.4.1
JDK 1.8
react 16.8.6
react-native 0.60.5

1.二、運行項目

(1)安裝 yarn、react-native 命令行工具git

$ npm install -g yarn react-native-cli
複製代碼

(2)設置 yarn 鏡像源github

$ yarn config set registry https://registry.npm.taobao.org --global
$ yarn config set disturl https://npm.taobao.org/dist --global
複製代碼

(3)安裝第三方插件web

進入到 react_native_project 目錄底下,安裝第三方插件:

$ yarn
複製代碼

(4)Android Studio 配置

Android Studio 的配置這裏再也不作介紹,能夠參考 react-native 官網

(5)編譯並運行項目

$ react-native run-android
複製代碼

(6)啓動項目

第 5 步後,若是真機或模擬器提示,Metro 沒有啓動,可關閉第 5 步開啓的 node 窗口,再重啓 Metro:

npm start
複製代碼

(7)服務端配套項目

記得 clone 本項目配套的服務端 express 項目,並啓動它。

2、React Native 介紹

「 Learn once, write anywhere 」,React Native 的定義就像是:學習 React ,同時掌握 web 與 app 兩種開發技能。 React Native 使用 React 的設計模式,開發者編寫 js 代碼,經過 React Native 的中間層轉化爲原生控件和操做,擁有接近原生開發的用戶體驗。下面引用官網上 4 條特性:

(1)使用 JavaScript 和 React 編寫原生移動應用

React Native 使你只使用 JavaScript 也能編寫原生移動應用。 它在設計原理上和 React 一致,經過聲明式的組件機制來搭建豐富多彩的用戶界面。

(2)React Native 應用是真正的移動應用

React Native 產出的並非「網頁應用」, 或者說「HTML5應用」,又或者「混合應用」。 最終產品是一個真正的移動應用,從使用感覺上和用 Objective-C 或 Java 編寫的應用相比幾乎是沒法區分的。 React Native 所使用的基礎 UI 組件和原生應用徹底一致。 你要作的就是把這些基礎組件使用 JavaScript 和 React 的方式組合起來。

(3)別再傻等編譯了

React Native 讓你能夠快速迭代開發應用。 比起傳統原生應用漫長的編譯過程,如今你能夠在瞬間刷新你的應用。開啓 Hot Reloading 的話,甚至能在保持應用運行狀態的狀況下熱替換新代碼!

(4)可隨時呼叫原生外援

React Native 完美兼容使用 Objective-C、Java 或是 Swift 編寫的組件。 若是你須要針對應用的某一部分特別優化,中途換用原生代碼編寫也很容易。 想要應用的一部分用原生,一部分用 React Native 也徹底沒問題。

3、項目功能

3.一、功能設計

考慮到更好的體驗 React Native 和 H5 在開發效率、功能性能、用戶體驗等方面的差別性,咱們對功能模塊進行精心設計,主要基於咱們如今實際項目的業務,結合移動端特有的特性。相關的模塊功能設計以下圖所示。

3.二、功能界面展現

截取一些功能展現以下:

3.三、項目結構目錄

咱們的項目目錄結構以下:

> ├─ .vscode		        編輯器配置						    
> ├─ android	                android 原生目錄
> ├─ ios			ios 原生目錄
> ├─node_modules		項目依賴包
> ├─ src                        代碼主目錄				
> │  ├─assets		        存放樣式文件
> │  │  ├─images                存放圖片
> │  │  └─styles	        樣式文件的 js 目錄
> │  │  ├─index.js              存放圖片路徑,能夠參照主頁面模塊寫法
> │  ├─components		存放塊級組件	
> │  ├─navigation	        存放導航配置		
> │  │  ├─ index.js		導航配置主文件			
> │  ├─pages			存放頁面級組件,不一樣模塊不一樣目錄
> │  └─utils		        存放工具方法		
> │  │  ├─ constant.js          一些常量配置,例如:服務器 IP 端口等
> │  │  ├─ globalVar.js         一些全局變量
> │  │  └─ request.js	        ajax 請求			
> ├─.eslintrc.js		eslint 配置	
> ├─.gitignore.js	        git 忽略配置							
> ├─index.js		        項目入口
> ├─package.json		項目依賴包配置
複製代碼

3.四、主要功能介紹

3.4.一、網盤功能

此模塊包含功能:文件夾建立、重命名、文件上傳、下載、側滑操做、長按列表操做、下拉刷新操做、文件預覽(包含圖片)等。

3.4.1.1 文件列表長按操做

(1) 使用插件

react-native-popup-menu
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-popup-menu
複製代碼
  • 邏輯實現

react_native_project/src/pages/netDisk/NetDisk.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import {
    Menu,
    MenuProvider,
    MenuOptions,
    MenuOption,
    MenuTrigger,
  } from 'react-native-popup-menu';

// render
<MenuProvider>
    <Menu>
        <MenuTrigger
            onAlternativeAction={() => this.getDirFile(rowData.item)}
            triggerOnLongPress={true}
            customStyles={triggerStyles}>
            <Image
                source={ rowData.item.icon }
                style={styles.thumbnail}
            />
            <View>
                <Text>{rowData.item.name}</Text>
                <Text>{dayjs(rowData.item.time).format('YYYY-MM-DD HH:mm:ss')}</Text>
            </View>
            <View>
                {
                    rowData.item.type === 'dir'?
                    <NBIcon type="AntDesign" name="right"/> : null
                }
            </View>
        </MenuTrigger>
        <MenuOptions customStyles={optionsStyles}>
            <MenuOption value={1} text='重命名' onSelect={() => {this.setState({
                modalVisible: true,
                fileItem: rowData.item,
                dialogType: 'Rename',
                hasInputText: true,
                inputVal: rowData.item.name,
                isSideSlip: false
            });}}/>
            <MenuOption value={2} text='刪除' onSelect={() => {
                this.setState({
                    modalVisible: true,
                    fileItem: rowData.item,
                    dialogType: 'Delete',
                    confirmText: '肯定刪除?',
                    hasInputText: false,
                    isSideSlip: false
                });
            }}/>
            <MenuOption value={3} text='下載'
                onSelect={() => this.downloadFile(rowData.item)} disabled={rowData.item.type === 'dir'}/>
        </MenuOptions>
    </Menu>
</MenuProvider>
複製代碼

(3)注意事項

  • triggerOnLongPress 設置爲 true 時,表示長按顯示下拉菜單,此時 onAlternativeAction 方法可用於單次觸發進入文件夾或者進行文件預覽相關功能。

(4)參考文檔

3.4.1.2 文件側滑操做

(1)使用插件

react-native-swipe-list-view
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-swipe-list-view
複製代碼
  • 邏輯實現

react_native_project/src/pages/netDisk/NetDisk.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import { SwipeListView } from 'react-native-swipe-list-view/lib/index';

// render
<SwipeListView
    style={styles.list}
    data={this.state.filesList}
    renderItem={ (rowData) => (
        <TouchableHighlight style={styles.rowFront} underlayColor={'#AAA'} > <View style={{flexDirection:'row',flex: 1,alignItems:'center'}}> <Text>{rowData.item.name}</Text> </View> </TouchableHighlight>
    )}
    renderHiddenItem={ (rowData, rowMap) => {
    return (
        <View style={styles.standaloneRowBack} key={rowData.item.time}> <NbButton style={[styles.backRightBtn, styles.backRightBtnLeft]} onPress={() =>{ this.setState({ modalVisible: true, fileItem: rowData.item, fileIndex: rowData.item.key, fileRowMap: rowMap, dialogType: 'Rename', hasInputText: true, inputVal: rowData.item.name, isSideSlip: true }); }}> <Text style={styles.backTextWhite}>重命名</Text> </NbButton> <NbButton style={[styles.backRightBtn, styles.backRightBtnRight]} onPress={() => { this.setState({ modalVisible: true, fileItem: rowData.item, fileIndex: rowData.item.key, fileRowMap: rowMap, dialogType: 'Delete', confirmText: '肯定刪除?', hasInputText: false, isSideSlip: true }); }}> <Text style={styles.backTextWhite}>刪除</Text> </NbButton> </View>
    );}
    }
    rightOpenValue={-150}
    stopRightSwipe={-150}
    disableRightSwipe={true}
    swipeToOpenPercent={20}
    swipeToClosePercent={0}
/>
複製代碼

(3)注意事項

  • 側滑操做完畢記得關閉側滑
// 關閉側滑
  closeRow(rowMap, rowKey) {
        if (rowMap[rowKey]) {
            rowMap[rowKey].closeRow();
        }
    }
複製代碼

(4)參考文檔

3.4.1.3 文件下載

(1) 使用插件

rn-fetch-blob
複製代碼

(2)功能實現

  • 插件安裝
yarn add rn-fetch-blob
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此配置完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/pages/netDisk/NetDisk.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import RNFetchBlob from 'rn-fetch-blob';

// 下載方法
 async actualDownload(item) {
    let dirs = RNFetchBlob.fs.dirs;
    const android = RNFetchBlob.android;
    RNFetchBlob.config({
        fileCache : true,
        path: `${dirs.DownloadDir}/${item.name}`,
        // android only options, these options be a no-op on IOS
        addAndroidDownloads : {
          // Show notification when response data transmitted
          notification : true,
          // Title of download notification
          title : '下載完成',
          // File description (not notification description)
          description : 'An file.',
          mime : getMimeType(item.name.split('.').pop()),
          // Make the file scannable by media scanner
          mediaScannable : true,
        }
      })
      .fetch('GET', `${CONSTANT.SERVER_URL}${item.path}`)
      .then(async(res) => {
            await android.actionViewIntent(res.path(), getMimeType(item.name.split('.').pop()));
        });
 }

複製代碼

(3)注意事項

  • 下載的文件沒法打開
// 問題
So basically this needs to be added to line 122-123 of file android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
// 解決辦法
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If above is not working do to the below step: overwrite the 121 line in android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 121 line
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 122 line

複製代碼

(4)參考文檔

3.4.1.4 文件上傳

(1)使用插件

// 獲取本機文件
react-native-file-selector
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-file-selector
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/pages/netDisk/NetDisk.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import RNFileSelector from 'react-native-file-selector';

// 選擇文件並上傳
RNFileSelector.Show(
    {
        title: '請選擇文件',
        onDone: (filePath) => {
            let data = new FormData();
            let file = { uri: 'file://' + filePath, type: 'multipart/form-data', name: escape(path.basename(filePath))};
            data.append('file', file);
            let options = {
                url: '/files/uploadFile',  // 請求 url
                data: data,
                tipFlag: true, // 默認統一提示,若是須要自定義提示,傳入 true
            };
            request(options).then(async (res) => {
                if (res.status == 200) {
                    await this.fetchData();
                    ToastAndroid.show(
                        '上傳成功',
                        ToastAndroid.SHORT,
                        ToastAndroid.CENTER
                        );
                }
            });
        },
        onCancel: () => {
            ToastAndroid.show(
                '取消上傳',
                ToastAndroid.SHORT,
                ToastAndroid.CENTER
                );
        }
    }
);
複製代碼

(3)注意事項

  • 爲了不中文字符文件名上傳後文件名不一致,能夠經過 escape 和 unescape 進行編碼和解碼。

(4)參考文檔

3.4.1.5 文件預覽(txt、office文件、pdf等)

(1) 使用插件

react-native-doc-viewer
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-doc-viewer
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/pages/netDisk/NetDisk.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import OpenFile from 'react-native-doc-viewer';

// 文件預覽
OpenFile.openDoc([{
    url: `${CONSTANT.SERVER_URL}${item.path}`,
    fileName: item.name.split('.').shift(),
    cache: false,
    fileType: item.name.split('.').pop()
}], (error) => {
    if (error) {
        this.setState({ animating: false });
        console.log(error);
        ToastAndroid.show('請先安裝相關應用軟件', ToastAndroid.SHORT);
    } else {
        this.setState({ animating: false });
        // ToastAndroid.show('該文件不支持預覽', ToastAndroid.SHORT);
    }
});
複製代碼

(3)注意事項

  • node_modules/react-native-doc-viewer/android/src/main/java/com/reactlibrary/RNReactNativeDocViewerModule.java 文件中 刪除 import com.facebook.react.views.webview.ReactWebViewManager;

(4)參考文檔

3.4.1.6 圖片預覽

(1) 使用插件

react-native-image-zoom-viewer
複製代碼

(2)功能實現

  • 插件安裝
react-native-image-zoom-viewer
複製代碼
  • 邏輯實現

react_native_project/src/pages/netDisk/NetDisk.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import ImageViewer from 'react-native-image-zoom-viewer';

// 圖片預覽方法
saveImg(url) {
    let promise = CameraRoll.saveToCameraRoll(url);
    promise.then((result) => {
        console.log(result);
        ToastAndroid.show('已保存到相冊', ToastAndroid.SHORT);
        }).catch((error) => {
        console.log(error);
        ToastAndroid.show('保存失敗', ToastAndroid.SHORT);
        });
}
// render
<Modal
    transparent={true}
    visible={imgModalVisible}
    onRequestClose={() => this.props.closeImg()}>
    <ImageViewer onCancel={()=> this.props.closeImg()} onClick={(onCancel) => {onCancel();}} onSave={(url) => this.saveImg(url)} saveToLocalByLongPress={true} imageUrls={images} index={imgIndex} doubleClickInterval={1000} menuContext={{ 'saveToLocal': '保存到相冊', 'cancel': '取消' }}/> </Modal>
複製代碼

(3)注意事項

  • 此插件 「圖片保存到相冊」 方法只適用於本機預覽的照片,遠程圖片保存方法可用 react-native 自帶方法 CameraRoll.saveToCameraRoll(url)。

(4)參考文檔

3.4.二、視聽學習

此模塊包含功能:音/視頻上傳、下載、刪除、判斷網絡、播放、全屏播放、轉向全屏播放、評論、分享等功能,其中上傳、下載、刪除功能在網盤模塊和試題模塊已說明。

3.4.2.1 視頻播放功能

(1)使用插件

react-native-video
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-video
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/pages/video/VideoPlayer.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import Video from 'react-native-video';

// 視頻進度時間方法
function formatTime(second) {
  let h = 0, i = 0, s = parseInt(second);
  if (s > 60) {
    i = parseInt(s / 60);
    s = parseInt(s % 60);
  }
  // 補零
  let zero = function (v) {
    return (v >> 0) < 10 ? '0' + v : v;
  };
  return [zero(h), zero(i), zero(s)].join(':');
}

// render
// 自帶參數和方法請看 api
<Video
    ref={(ref) => this.videoPlayer = ref}
    source={{uri: CONSTANT.SERVER_URL + '/' + this.state.videoUrl}}
    rate={this.state.playRate}
    volume={this.state.volume}
    muted={this.state.isMuted}
    paused={!this.state.isPlaying}
    resizeMode={'contain'}
    playWhenInactive={false}
    playInBackground={false}
    ignoreSilentSwitch={'ignore'}
    progressUpdateInterval={250.0}
    onLoadStart={this._onLoadStart}
    onLoad={this._onLoaded}
    onProgress={this._onProgressChanged}
    onEnd={this._onPlayEnd}
    onError={this._onPlayError}
    onBuffer={this._onBuffering}
    style={{ width: this.state.videoWidth, height: this.state.videoHeight}}
/>

複製代碼

(3)參考文檔

3.4.2.2 視頻最大化、轉向

(1) 使用插件

react-native-orientation
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-orientation
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/pages/video/VideoPlayer.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import Orientation from 'react-native-orientation';

// 點擊工具欄上的全屏按鈕
  onControlShrinkPress() {
    if (this.state.isFullScreen) {
      Orientation.lockToPortrait();
    } else {
        Orientation.lockToLandscapeRight();
    }
  }

  // 屏幕旋轉時寬高會發生變化,能夠在onLayout的方法中作處理,比監聽屏幕旋轉更加及時獲取寬高變化
  _onLayout = (event) => {
    //獲取根View的寬高
    let {width, height} = event.nativeEvent.layout;
    // 通常設備橫屏下都是寬大於高,這裏能夠用這個來判斷橫豎屏
    let isLandscape = (width > height);
    if (isLandscape && !this.showKeyboard){
      this.setState({
        videoWidth: width,
        videoHeight: height,
        isFullScreen: true,
      });
    } else {
      this.setState({
        videoWidth: width,
        videoHeight: width * 9/16,
        isFullScreen: false,
      });
    }
    Orientation.unlockAllOrientations();
  };

複製代碼

(3)參考文檔

3.4.2.3 微信、朋友圈分享

(1) 使用插件

react-native-wechat
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-wechat
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/components/video/VideoShare.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import * as WeChat from 'react-native-wechat';

// const wxAppId = ''; // 微信開放平臺註冊的app id
// const wxAppSecret = ''; // 微信開放平臺註冊獲得的app secret
// WeChat.registerApp(wxAppId);

// 分享
shareItemSelectedAtIndex(index) {
    // this.props.onShareItemSelected && this.props.onShareItemSelected(index);
    WeChat.isWXAppInstalled().then((isInstalled) => {
        this.setState({
          isWXInstalled: isInstalled
        });
        if (isInstalled && index === 0) {
          WeChat.shareToSession({
                title: this.state.videoTitle,
                type: 'video',
                videoUrl: CONSTANT.SERVER_URL + '/' + this.state.videoUrl
            }).catch((error) => {
                console.log(error.message);
            });
        } else if (isInstalled && index === 1) {
            WeChat.shareToTimeline({
                  title: this.state.videoTitle,
                  type: 'video',
                  videoUrl: CONSTANT.SERVER_URL + '/' + this.state.videoUrl
              }).catch((error) => {
                  console.log(error.message);
              });
          } else {
          console.log('微信未安裝');
        }
      });
  }

複製代碼

(3)參考文檔

3.4.三、試題模塊

3.4.3.一、拍照 & 上傳圖片 建立試題功能

(1)使用插件

react-native-image-crop-picker 
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-image-crop-picker 
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/components/exam/ImageAudioTab.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import ImagePicker from 'react-native-image-crop-picker';

// 從相冊選擇圖片
ImagePicker.openPicker(paramObj).then(image => {
	this.props.handleImage(qsIndex, image);
}).catch(err => {
	console.log(err);
});

// 調用攝像頭功能
openCamera(qsIndex) {
	ImagePicker.openCamera({
		width: 300,
		height: 400,
		cropping: true,
	}).then(image => {
		this.props.handleImage(qsIndex, image);
	}).catch(err => {
		console.log(err);
	});
}
複製代碼

(3)注意事項

  • 拍照或者一次只選擇一張圖片時,才能進行圖片的剪裁操做,一次選擇多張圖片沒法進行圖片的剪裁操做;

(4)參考文檔

3.4.3.二、語音錄入 建立試題功能

(1) 使用插件

react-native-audio // 語音錄入
react-native-sound // 語音播放
react-native-spinkit // 動畫效果
複製代碼

(2)功能實現

  • 插件安裝
yarn add react-native-audio react-native-sound react-native-spinkit
複製代碼
  • 從新編譯

由於語音錄入插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/components/exam/ImageAudioTab.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import { AudioRecorder, AudioUtils } from 'react-native-audio';
import Sound from 'react-native-sound';
import Spinkit from 'react-native-spinkit';

// 音頻路徑配置
prepareRecordingPath = (path) => {
	const option = {
		SampleRate: 44100.0, //採樣率
		Channels: 2, //通道
		AudioQuality: 'High', //音質
		AudioEncoding: 'aac', //音頻編碼
		OutputFormat: 'mpeg_4', //輸出格式
		MeteringEnabled: false, //是否計量
		MeasurementMode: false, //測量模式
		AudioEncodingBitRate: 32000, //音頻編碼比特率
		IncludeBase64: true, //是不是base64格式
		AudioSource: 0, //音頻源
	};
	AudioRecorder.prepareRecordingAtPath(path, option);
}

// 開始錄音
startSoundRecording(qsIndex, stemAudio) {
	if (stemAudio.length >= 5) {
		ToastAndroid.show('每道題最多 5 段語音哦', ToastAndroid.SHORT);
		return;
	}
	console.log('startSoundRecording....');
	// 請求受權
	AudioRecorder.requestAuthorization()
		.then(isAuthor => {
			if (isAuthor) {
				this.prepareRecordingPath(this.audioPath + qsIndex + '_' + stemAudio.length + '.aac');
				// 錄音進展
				AudioRecorder.onProgress = (data) => {
					this.recordTime = Math.floor(data.currentTime);
				};
				// 完成錄音
				AudioRecorder.onFinished = (data) => {
					// data 返回須要上傳到後臺的錄音數據;
					this.isRecording = false;
					if (!this.recordTime) {
						ToastAndroid.show('錄音時間過短...', ToastAndroid.SHORT);
						return;
					}
					this.props.handleAudio(qsIndex, data.audioFileURL, this.recordTime);
					// 重置爲 0 
					this.recordTime = 0;
				};
				// 錄音
				AudioRecorder.startRecording();
				this.isRecording = true;
			}
		});
}

// 結束錄音
stopSoundRecording() {
	console.log('stopSoundRecording....');
	// 已經被節流操做攔截,沒有在錄音
	if (!this.isRecording) {
		return;
	}
	AudioRecorder.stopRecording();
}

// 播放錄音
playSound(qsIndex, index, stemAudio, audioFlag, path) {
	this.props.changeAudioState(qsIndex, index, 2);
	let whoosh = new Sound(path.slice(7), '', (err) => {
		if (err) {
			return console.log(err);
		}
		whoosh.play(success => {
			if (success) {
				console.log('success - 播放成功');
			} else {
				console.log('fail - 播放失敗');
			}
			this.props.changeAudioState(qsIndex, index, 1);
		});
	});
}
複製代碼

(3)注意事項

  • 語音錄入若是沒有作節流操做,短期內不斷重複點擊開始錄入和結束錄入,會致使錄音出錯,因此咱們監聽用戶長按操做時,纔打開手機的錄音器,開始錄音;

(4)參考文檔

3.4.3.三、圖表實現成績統計

(1) 使用插件

victory-native // 圖標繪製插件
react-native-svg // svg 圖片繪製
複製代碼

(2)功能實現

  • 插件安裝
yarn add victory-native react-native-svg
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

react_native_project/src/pages/exam/ResultStatistics.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 插件引入
import { 
  VictoryPie, 
  VictoryLegend, 
  VictoryTooltip 
} from 'victory-native';

// 圖形繪製組件使用
<VictoryLegend
	orientation="vertical"
	data={[
	  {
		name: '不及格 < 60 分',
		symbol: { fill: colorScale[0], type: 'square' },
	  },
	  {
		name: '及格 60 - 75 分',
		symbol: { fill: colorScale[1], type: 'square' },
	  },
	  {
		name: '良好 75 - 85 分',
		symbol: { fill: colorScale[2], type: 'square' },
	  },
	  {
		name: '優秀 > 85 分',
		symbol: { fill: colorScale[3], type: 'square' },
	  }
	]}
	width={180}
	height={125}
/>
<VictoryPie
	colorScale={colorScale}
	data={[
	  { y: this.state.result[3], label: '不及格:' + this.state.result[3] + '人'},
	  { y: this.state.result[2], label: '及格:' + this.state.result[2] + '人' },
	  { y: this.state.result[1], label: '良好:' + this.state.result[1] + '人' },
	  { y: this.state.result[0], label: '優秀:' + this.state.result[0] + '人' }
	]}
	innerRadius={60}
	height={300}
	width={345}
	animate={{
	  duration: 2000
	}}
	labelComponent={
		<VictoryTooltip
			active={({ datum }) => datum.y === 0 ? false : true}
			constrainToVisibleArea={true}
			flyoutHeight={30}
			flyoutStyle={{ strokeWidth: 0.1}}
		/>
	}
/>
複製代碼

(3)注意事項

  • 暫無

(4)參考文檔

3.4.四、其它

3.4.4.一、電話 & 短信功能

(1)使用插件

Linking  // react native 自帶的插件 
複製代碼

(2)功能實現

  • 邏輯實現

react_native_project/src/components/user/ListItem.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

// 撥打電話功能 or 短信功能
call(flag) {
let tel = flag === 1 ? 'tel:10086' : 'smsto:10086';
Linking.canOpenURL(tel).then(supported => {
  if (!supported) {
	ToastAndroid.show.show('您未受權通話和短信權限');
  } else {
	return Linking.openURL(tel);
  }
}).catch(err => console.error('An error occurred', err));
}
複製代碼

(3)注意事項

  • 暫無

(4)參考文檔

3.4.4.二、手機定位功能

(1) 使用插件

  • 暫無,封裝 Android 原生方法進行實現;

(2)功能實現

  • 獲取定位功能邏輯實現

react_native_project/android/app/src/main/java/com/react_native_project/module 目錄中建立實現類 LocationModule.java,須要注意的是這個類須要實現 ReactContextBaseJavaModule 這個類:

public class LocationModule extends ReactContextBaseJavaModule {
    private final ReactApplicationContext mContext;
    public LocationModule(ReactApplicationContext reactContext) {
        super(reactContext);
        mContext = reactContext;
    }

    /** * @return js調用的模塊名 */
    @Override
    public String getName() {
        return "LocationModule";
    }


    /** * 使用ReactMethod註解,使這個方法被js調用 */
    @ReactMethod
    public void getLocation(Callback locationCallback) {
            // 省略一些邏輯實現 ...
            locationCallback.invoke(lat,lng,country,locality);
        }else{
            locationCallback.invoke(false);
        }
    }
}
複製代碼
  • 模塊註冊

對剛剛實現定位功能的模塊進行註冊,在 react_native_project/android/app/src/main/java/com/react_native_project/module 目錄中建立註冊包管理類 LocationReactPackage .java,相關邏輯以下:

public class LocationReactPackage implements ReactPackage {
    /** * @param reactContext 上下文 * @return 須要調用的原生控件 */
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    /** * @param reactContext 上下文 * @return 須要調用的原生模塊 */
    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new LocationModule(reactContext));
        return modules;
    }
}
複製代碼
  • 添加包管理類

react_native_project/android/app/src/main/java/com/react_native_project/MainApplication.java 中添加包管理類,相關邏輯以下:

protected List<ReactPackage> getPackages() {
  @SuppressWarnings("UnnecessaryLocalVariable")
  List<ReactPackage> packages = new PackageList(this).getPackages();
  packages.add(new LocationReactPackage());
  return packages;
}
複製代碼
  • react native 中使用封裝類

咱們在 react_native_project/src/components/user/ListItem.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

import { NativeModules } from 'react-native';

// 獲取地理位置
showLocation() {
 NativeModules.LocationModule.getLocation((lat, lng, country, locality) => {
  let str = '獲取位置信息失敗,您可能手機位置信息沒有開啓!';
  if (lat && lng) {
	str = country + ',' + locality + ',緯度:' + lat + ',' + '經度:' + lng;
  }
  ToastAndroid.show(str, ToastAndroid.SHORT);
 });
}
複製代碼

(3)注意事項

  • 由於該功能是由 Android 原生編碼封裝,因此封裝完 Android 原生類,須要進行編譯,再在 JS 端進行調用,纔有效果。

(4)參考文檔

3.4.4.三、在線升級

(1) 使用插件

rn-fetch-blob
複製代碼

(2)功能實現

  • 插件安裝
yarn add rn-fetch-blob
複製代碼
  • 從新編譯

由於該插件涉及到 Android 原生功能,因此添加完該插件,須要從新編譯 Android。

  • 邏輯實現

咱們實如今線升級功能的大概邏輯是,在 app 管理端上傳 apk 安裝包,而後點擊發布,這時服務端會經過 websocket 將最新發布的版本號通知 app,app 收到最新版本號,會跟當前的 app 版本比較,若是當前版本號小於最新版本號,則會彈窗提示有最新版本,詢問用戶是否下載安裝,用戶若是確認安裝最新版本,則會從服務器下載最新的 apk,並進行安裝。在 react_native_project/src/components/user/ListItem.js 組件中實現相應邏輯,關鍵代碼及註釋以下:

import RNFetchBlob from 'rn-fetch-blob';

  checkUpdate = () => {
    const android = RNFetchBlob.android;
    //下載成功後文件所在path
    const downloadDest = `${ RNFetchBlob.fs.dirs.DownloadDir }/app_release.apk`;

    RNFetchBlob.config({
      //配置手機系統通知欄下載文件通知,下載成功後點擊通知可運行apk文件
      addAndroidDownloads: {
        useDownloadManager: true,
        title: 'RN APP',
        description: 'An APK that will be installed',
        mime: 'application/vnd.android.package-archive',
        path: downloadDest,
        mediaScannable: true,
        notification: true
      }
    }).fetch(
      'GET',
       CONSTANT.SERVER_URL+'/appVersion/download?path='+this.newVersionInfo.path
    ).then(res => {
      //下載成功後自動打開運行已下載apk文件
      android.actionViewIntent(
        res.path(),
        'application/vnd.android.package-archive'
      );
    });
  }
複製代碼

(3)注意事項

  • 暫無

(4)參考文檔

4、React Native 開發踩的坑

4.一、運行 react-native run-android 出現錯誤:Task :app:mergeDebugAssets FAILED OR Task :app:processDebugResources FAILED 。

解決:

cd android && ./gradlew clean
cd .. && react-native run-android
複製代碼

4.二、若是手機真機出現鏈接不上開發開發服務器的狀況。

解決:

命令窗口運行如下命令:

adb reverse tcp:8081 tcp:8081
複製代碼

4.三、kotlin 相關 jar 包沒法下載。

解決:

對應的插件的 android/build.gradle 配置阿里雲倉庫(例如遇到這個問題時,是在插件 react-native-webview)

// Maven中心倉庫牆內版
  maven { url "https://maven.aliyun.com/repository/central"  }
// jCenter中心倉庫牆內版
  maven { url "https://maven.aliyun.com/repository/jcenter"  }
  maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
複製代碼

4.四、文件預覽插件:react-native-doc-viewer安裝完 run-android 編譯失敗。

解決:

Could be fixed by removing the import in node_modules/react-native-doc-viewer/android/src/main/java/com/reactlibrary/RNReactNativeDocViewerModule.java

Remove the ununsed import:

import com.facebook.react.views.webview.ReactWebViewManager;

複製代碼

4.五、第三方插件 rn-fetch-blob 下載文檔沒法打開。

解決:

So basically this needs to be added to line 122-123 of file android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If above is not working do to the below step: overwrite the 121 line in android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 121 line
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 122 line
複製代碼

5、總結

本文主要基於 React Native 框架的實踐進行總結,分享了 React Native 理念、React Native 項目的功能介紹、React Native 項目編譯以及 React Native 存在的一些坑,但願對徹底閱讀完的你有啓發和幫助,若是有不足,歡迎批評、指正、交流!

姐妹篇《 Weex 實踐總結 》,能夠進行 React Native 和 Weex 的對比。

辛苦整理良久,還望手動點贊鼓勵~

博客 github地址爲:github.com/fengshi123/… ,彙總了做者的全部博客,也歡迎關注及 star ~

本項目 github 地址爲:github.com/fengshi123/…

配套的服務端 express 項目 github 地址爲:github.com/fengshi123/…

相關文章
相關標籤/搜索