然后找一張圖片 head_default.png(命名隨意)作為默認頭像,放到工程的 HeadImage/android/app/src/main/res/drawable 目錄下,rn默認工程沒有drawable可以自己新建一個,然后webstorm選擇工程根目錄打開,修改index.android.js代碼
export default class HeadImage extends Component { render() { return ( <View style={styles.container}> <TouchableOpacity onPRess={this._clickImage}> <Image source={{uri: 'head_default'}} style={{width:50,height:50}}/> </TouchableOpacity> </View> ); } _clickImage(){ console.log("click image..."); }}修改了render方法,t添加_clickImage方法,TouchableOpacity和Image組件別忘了import
注意Image的uri要與自己放在HeadImage/android/app/src/main/res/drawable目錄下的默認頭像圖片命名一致,之后編譯運行(必須編譯運行,因為加了資源圖片,只是reload是不顯示圖片的),運行結果將顯示默認頭像,點擊打出log接下來實現與原生的交互,參考 http://reactnative.cn/docs/0.31/native-modules-android.html#content
androidstudio打開工程的android目錄
![]()
新建兩個類,HeadImageModule.java和HeadImagePackage.java,分別繼承ReactContextBaseJavaModule和ReactPackage,之后在Mainapplication.java里面注冊,各部分代碼如下:
HeadImageModule.java
public%20class%20HeadImageModule%20extends%20ReactContextBaseJavaModule%20{%20%20%20%20public%20HeadImageModule(ReactApplicationContext%20reactContext)%20{%20%20%20%20%20%20%20%20super(reactContext);%20%20%20%20}%20%20%20%20@Override%20%20%20%20public%20String%20getName()%20{%20%20%20%20%20%20%20%20return%20"HeadImageModule";%20//注意這里的返回值%20%20%20%20}%20%20%20%20@ReactMethod%20%20%20%20public%20void%20callCamera()%20{%20//%20調用相機的方法%20%20%20%20%20%20%20%20Log.d("","call%20camera...");%20%20%20%20}}HeadImagePackage.java
public%20class%20HeadImagePackage%20implements%20ReactPackage%20{%20%20%20%20@Override%20%20%20%20public%20List<NativeModule>%20createNativeModules(ReactApplicationContext%20reactContext)%20{%20%20%20%20%20%20%20%20List<NativeModule>%20modules%20=%20new%20ArrayList<>();%20%20%20%20%20%20%20%20modules.add(new%20HeadImageModule(reactContext));%20%20%20%20%20%20%20%20return%20modules;%20%20%20%20}%20%20%20%20@Override%20%20%20%20public%20List<Class<?%20extends%20JavaScr//%20保存圖片的sd卡路徑private%20static%20final%20String%20HEAD_IMAGE_PATH%20=%20Environment.getExternalStorageDirectory().getAbsolutePath()%20+%20"/HeadImage/";//%20保存圖片的名稱private%20static%20final%20String%20HEAD_IMAGE_NAME%20=%20"head_image.png";// startActivityForResult 的 requestCodeprivate static final int REQUEST_CODE_CAMERA = 0;private static final int REQUEST_CODE_GALLERY = 1;private static final int REQUEST_CODE_CROP = 2;接下來實現callCamera方法,注意要讓js可以調用必須加@ReactMethod,Promise是與js交互有關的,參考:http://reactnative.cn/docs/0.31/native-modules-android.html#content
@ReactMethodpublic void callCamera(Promise promise) { recursionDeleteFile(); // 刪除目錄下除了頭像圖片的其他臨時圖片 Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);//啟動相機的intent if (isPathExists()) { // 判斷常量定義的路徑是否存在,不存在就創建,然后返回true mFullPath = HEAD_IMAGE_PATH + System.currentTimeMillis() + ".png"; // 臨時圖片 mUri = Uri.fromFile(new File(mFullPath)); intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); Activity activity = getCurrentActivity(); if (activity != null) { mPromise = promise; activity.startActivityForResult(intent, REQUEST_CODE_CAMERA); } }}執行完這個方法就可以啟動相機了,方法中使用臨時圖片是因為拍照->裁剪->完成這個過程用戶可能在某一步取消操作,避免原來的頭像被替換,但是這樣每次調用相機都會創建一個臨時圖片,為了不使sd卡存頭像圖片的文件夾越來越大,所以編寫了recursionDeleteFile()方法每次做一次遞歸刪除,刪除臨時圖片,代碼就不貼了,后面會給出源碼地址。拍照點擊完成之后,就該去onActivityResult里面處理了,rn提供了一個接口實現監聽onActivityResult,還是看http://reactnative.cn/docs/0.31/native-modules-android.html#content (或者直接在MainActivity里面重寫onActivityResult方法應該也是可以的,有興趣的可以嘗試一下)
在HeadImageModule.java構造方法里面添加如下代碼
reactContext.addActivityEventListener(new BaseActivityEventListener() { @Override public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_CAMERA) { // 調用相機回調 if (resultCode == Activity.RESULT_OK) { // *************1.拍照完成,將進入裁剪界面 activity.startActivityForResult(cropImage(mUri), REQUEST_CODE_CROP);// 啟動裁剪界面 } else if (resultCode == Activity.RESULT_CANCELED) { // 拍照界面點擊取消 mPromise.resolve(null); // mFullPath就是callCamera里面定義的臨時圖片路徑 // 如果沒有取消拍照,那么就不執行這里,臨時圖片的刪除將在下次調用相機的時候,所以與recursionDeleteFile()不重復 new File(mFullPath).delete(); } } else if (requestCode == REQUEST_CODE_CROP) { // ************2.裁剪完成 if (resultCode == Activity.RESULT_OK) { // uri存的是臨時圖片路徑,返回給js代碼,這里有個問題,稍后再說 mPromise.resolve(mUri.toString()); // 將臨時圖片復制一份,保存為最終的頭像圖片 saveHeadImage(); } else if (resultCode == Activity.RESULT_CANCELED) { mPromise.resolve(null); new File(mFullPath).delete(); } } }});拍照完成將調用系統的裁剪功能activity.startActivityForResult(cropImage(mUri), REQUEST_CODE_CROP);cropImage(mUri)方法的實現
private Intent cropImage(Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); intent.putExtra("outputX", 800); intent.putExtra("outputY", 800); intent.putExtra("return-data", false); intent.putExtra("scale", true); intent.putExtra("scaleUpIfNeeded", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mFullPath))); intent.putExtra("outputFormat", "png"); return intent;}裁剪完成基本就結束了,上面說到有個問題,就是下面代碼裁剪完成的時候返回給js的圖片是臨時圖片,而不是saveHeadImage()保存最終圖片之后返回最終的圖片head_image.png if (resultCode == Activity.RESULT_OK) { // uri存的是臨時圖片路徑,返回給js代碼,這里有個問題,稍后再說 mPromise.resolve(mUri.toString()); // 將臨時圖片復制一份,保存為最終的頭像圖片 saveHeadImage(); } 看js代碼<Image source={{uri: 'head_default'}} style={{width:50,height:50}}/>如果uri設置的是最終頭像head_image.png,那么顯示到界面之后,替換sd卡上的最終頭像圖片,命名不變,這時候刷新界面,圖片還是顯示替換之前的頭像,按返回鍵退出,再啟動應用,也是顯示之前的頭像,除非殺死進程再啟動,猜測這個應該跟android的內存機制有關,這就是Java部分就返回臨時圖片的原因。
到這里,頭像圖片已經成功的保存到sd卡上了,接下來就是js顯示的實現了,先理一下,js需要處理的圖片包括三個:默認頭像,sd卡存的臨時頭像,sd卡存的最終頭像。很容易想到,先判斷sd卡的最終頭像是否存在,不存在就用默認頭像,也就是最終頭像優先級高于默認頭像,至于臨時頭像,通過上面的介紹知道只有拍照并且完成裁剪之后才會有臨時頭像傳給js,而這時候臨時的和最終的一樣,其他時候在js里面都是空,所以可以把臨時的優先級看成最高,結果就是先判斷臨時圖片的存在,再判斷最終圖片的存在,都不存在的話使用默認圖片。
邏輯理順了就開始寫代碼,可以新建一個自己的組件,webstorm中在項目的根目錄下新建MyImage.js(命名和路徑隨意),代碼如下:
import React, {Component, PropTypes} from 'react';import { View, StyleSheet, Image, NativeModules,} from 'react-native';export default class MyImage extends Component { constructor(props) { super(props); this.state = { uri: null, }; } static defaultProps = { uri: null, }; static propTypes = { uri: PropTypes.string, imageStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), } async componentWillReceiveProps() { let isExists = await NativeModules.HeadImageModule.isImageExists(); if (this.props.uri !== null) { this.setState({ uri: this.props.uri }); } else if (isExists) { this.setState({ uri: await NativeModules.HeadImageModule.getImageUri() }); } else { this.setState({ uri: 'head_default' }); } } render() { return ( <Image source={{uri: this.state.uri}} style={this.props.imageStyle}/> ); }}需要的文件操作可以使用java然后js調用(仿照camera),自己的組件寫好就可以去用了,修改index.android.js代碼:首先import自己的組件
import MyImage from './MyImage'export default class HeadImage extends Component { constructor(props) { super(props); this.state = { headImageUri: null, }; } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={this._clickImage.bind(this)}> <MyImage uri={this.state.headImageUri} imageStyle={{width: 100,height: 100}}/> </TouchableOpacity> </View> ); } async _clickImage() { this.setState({ headImageUri: await NativeModules.HeadImageModule.callCamera() // 相機拍照 // headImageUri: await NativeModules.HeadImageModule.callGallery() // 相冊選擇圖片 }); } componentDidMount() { this.setState({ code: this.props.code }); }}相冊部分和相機相似,到這里就差不多結束了,代碼還有一些地方可以完善,這里就不繼續了,簡單列出一下: js部分可以把負責點擊事件處理的TouchableOpacity 寫到自定義組件MyImage里面,當然相應的處理邏輯也在MyImage里面,這樣可以讓自定義組件使用起來更方便
可以給MyImage增加一個設置頭像存儲路徑的屬性,然后傳給Java部分,java就不用使用常量將頭像的存儲路徑寫死了,可以更加靈活
可以在頭像的存儲路徑下創建一個.nomedia文件,避免頭像圖片被系統圖庫掃描到
源碼地址:
http://download.csdn.net/download/simple_simple_simple/9749571
https://github.com/developerzjy/react_native_android_headImage
新聞熱點
疑難解答