摘要:,已過期,請使用代替。解決方案安卓增加屬性并設置為官方解釋當圖片實際尺寸和容器樣式尺寸不一致時,決定以怎樣的策略來調整圖片的尺寸。
本文均為RN開發(fā)過程中遇到的問題、坑點的分析及解決方案,各問題點之間無關聯(lián),希望能幫助讀者少走彎路,持續(xù)更新中... (2019年3月29日更新)
原文鏈接:http://www.kovli.com/2018/06/...
作者:Kovli
- 如何在原生端(iOS和android兩個平臺)使用ReactNative里的本地圖片(路徑類似require("./xxximage.png"))。在ReactNative開發(fā)過程中,有時需要在原生端顯示RN里的圖片,這樣的好處是可以通過熱更新來更新APP里的圖片,而不需要發(fā)布原生版本,而ReactNative里圖片路徑是相對路徑,類似"./xxximage.png"的寫法,原生端是無法解析這類路徑,那么如果將RN的圖片傳遞給原生端呢?
解決方案:
1、圖片如果用網(wǎng)絡圖,那只需要將url字符串地址傳遞給原生即可,這種做法需要時間和網(wǎng)絡環(huán)境加載圖片,不屬于本地圖片,不是本方案所追求的最佳方式。
2、懶人做法是把RN的本地圖片生成base64字符串然后傳遞給原生再解析,這種做法如果圖片太大,字符串會相當長,同樣不認為是最佳方案。
其實RN提供了相關的解決方法,如下:
RN端
const myImage = require("./my-image.png"); const resolveAssetSource = require("react-native/Libraries/Image/resolveAssetSource"); const resolvedImage = resolveAssetSource(myImage); NativeModules.NativeBridge.showRNImage(resolvedImage);
iOS端
#importRCT_EXPORT_METHOD(showRNImage:(id)rnImageData){ dispatch_async(dispatch_get_main_queue(), ^{ UIImage *rnImage = [RCTConvert UIImage:rnImageData]; ... }); }
安卓端
第一步,從橋接文件獲取到uri地址
@ReactMethod public static void showRNImage(Activity activity, ReadableMap params){ String rnImageUri; try { //圖片地址 rnImageUri = params.getString("uri"); Log.i("Jumping", "uri : " + uri); ... } catch (Exception e) { return; } }
第二步,創(chuàng)建JsDevImageLoader.java
package com.XXX; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.StrictMode; import android.support.annotation.NonNull; import android.util.Log; import com.XXX.NavigationApplication; import java.io.IOException; import java.net.URL; public class JsDevImageLoader { private static final String TAG = "JsDevImageLoader"; public static Drawable loadIcon(String iconDevUri) { try { StrictMode.ThreadPolicy threadPolicy = StrictMode.getThreadPolicy(); StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitNetwork().build()); Drawable drawable = tryLoadIcon(iconDevUri); StrictMode.setThreadPolicy(threadPolicy); return drawable; } catch (Exception e) { Log.e(TAG, "Unable to load icon: " + iconDevUri); return new BitmapDrawable(); } } @NonNull private static Drawable tryLoadIcon(String iconDevUri) throws IOException { URL url = new URL(iconDevUri); Bitmap bitmap = BitmapFactory.decodeStream(url.openStream()); return new BitmapDrawable(NavigationApplication.instance.getResources(), bitmap); } }
第三步,導入ResourceDrawableIdHelper.java
package com.xg.navigation.react;// Copyright 2004-present Facebook. All Rights Reserved. import android.content.Context; import android.graphics.drawable.Drawable; import android.net.Uri; import com.facebook.common.util.UriUtil; import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; /** * Direct copy paste from react-native, because they made that class package scope. -_-" * Can be deleted in react-native ^0.29 */ public class ResourceDrawableIdHelper { public static final ResourceDrawableIdHelper instance = new ResourceDrawableIdHelper(); private MapmResourceDrawableIdMap; public ResourceDrawableIdHelper() { mResourceDrawableIdMap = new HashMap<>(); } public int getResourceDrawableId(Context context, @Nullable String name) { if (name == null || name.isEmpty()) { return 0; } name = name.toLowerCase().replace("-", "_"); if (mResourceDrawableIdMap.containsKey(name)) { return mResourceDrawableIdMap.get(name); } int id = context.getResources().getIdentifier( name, "drawable", context.getPackageName()); mResourceDrawableIdMap.put(name, id); return id; } @Nullable public Drawable getResourceDrawable(Context context, @Nullable String name) { int resId = getResourceDrawableId(context, name); return resId > 0 ? context.getResources().getDrawable(resId) : null; } public Uri getResourceDrawableUri(Context context, @Nullable String name) { int resId = getResourceDrawableId(context, name); return resId > 0 ? new Uri.Builder() .scheme(UriUtil.LOCAL_RESOURCE_SCHEME) .path(String.valueOf(resId)) .build() : Uri.EMPTY; } }
第四步,創(chuàng)建BitmapUtil.java
package com.XXX; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.MediaStore; import android.text.TextUtils; import com.XXX.NavigationApplication; import com.XXX.JsDevImageLoader; import com.XXX.ResourceDrawableIdHelper; import java.io.IOException; public class BitmapUtil { private static final String FILE_SCHEME = "file"; public static Drawable loadImage(String iconSource) { if (TextUtils.isEmpty(iconSource)) { return null; } if (NavigationApplication.instance.isDebug()) { return JsDevImageLoader.loadIcon(iconSource); } else { Uri uri = Uri.parse(iconSource); if (isLocalFile(uri)) { return loadFile(uri); } else { return loadResource(iconSource); } } } private static boolean isLocalFile(Uri uri) { return FILE_SCHEME.equals(uri.getScheme()); } private static Drawable loadFile(Uri uri) { Bitmap bitmap = BitmapFactory.decodeFile(uri.getPath()); return new BitmapDrawable(NavigationApplication.instance.getResources(), bitmap); } private static Drawable loadResource(String iconSource) { return ResourceDrawableIdHelper.instance.getResourceDrawable(NavigationApplication.instance, iconSource); } public static Bitmap getBitmap(Activity activity, String uri) { if (activity == null || uri == null || TextUtils.isEmpty(uri)) { return null; } Uri mImageCaptureUri; try { mImageCaptureUri = Uri.parse(uri); } catch (Exception e) { e.printStackTrace(); return null; } if (mImageCaptureUri == null) { return null; } Bitmap bitmap = null; try { bitmap = MediaStore.Images.Media.getBitmap(activity.getContentResolver(), mImageCaptureUri); } catch (IOException e) { e.printStackTrace(); return null; } return bitmap; } }
第五步,使用第一步里的rnImageUri地址
... BitmapUtil.loadImage(rnImageUri) ...
第六步,顯示圖片
import android.widget.RelativeLayout; import android.support.v7.widget.AppCompatImageView; import android.graphics.drawable.Drawable; ... final RelativeLayout item = (RelativeLayout) mBottomBar.getChildAt(i); final AppCompatImageView itemIcon = (AppCompatImageView) item.getChildAt(0); itemIcon.setImageDrawable(BitmapUtil.loadImage(rnImageUri)); ...- 升級舊RN版本到目前最新的0.57.8如果采用手動升級需要注意如下。
I upgraded from react-naitve 0.55.4 to react-native 0.57.0 and I get this error
bundling failed: Error: The "decorators" plugin requires a "decoratorsBeforeExport" option, whose value must be a boolean. If you are migrating from Babylon/Babel 6 or want to use the old decorators proposal, you should use the "decorators-legacy" plugin instead of "decorators".
解決方案:參考如下例子
First install the new proposal decorators with npm install @babel/plugin-proposal-decorators --save-dev or yarn add @babel/plugin-proposal-decorators --dev
Then, inside of your .babelrc file, change this:
{ "presets": ["react-native"], "plugins": ["transform-decorators-legacy"] } To this: { "presets": [ "module:metro-react-native-babel-preset", "@babel/preset-flow" ], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy" : true }] ] }
EDIT:
After you"ve updated your .babelrc file, make sure to add preset-flow as well with the command yarn add @babel/preset-flow --dev or npm install @babel/preset-flow --save-dev
- ReactNative輸入框TextInput點擊彈起鍵盤,如果鍵盤遮擋了重要位置,如何讓界面自動跟隨鍵盤調整?使用這個組件KeyboardAvoidingView
本組件用于解決一個常見的尷尬問題:手機上彈出的鍵盤常常會擋住當前的視圖。本組件可以自動根據(jù)鍵盤的位置,調整自身的position或底部的padding,以避免被遮擋。
解決方案:參考如下例子
- ReactNative輸入框TextInput點擊彈起鍵盤,然后點擊其他子組件,例如點擊提交按鈕,會先把鍵盤收起,再次點擊提交按鈕才響應提交按鈕,得點擊兩次,如何做到點擊提交按鈕的同時收起鍵盤并響應按鈕?... ...
這個問題關鍵在ScrollView的keyboardShouldPersistTaps屬性
,首先TextInput的特殊性(有鍵盤彈起)決定了其最好包裹在ScrollView里,其次如果當前界面有軟鍵盤,那么點擊scrollview后是否收起鍵盤,取決于keyboardShouldPersistTaps屬性的設置。(譯注:很多人反應TextInput無法自動失去焦點/需要點擊多次切換到其他組件等等問題,其關鍵都是需要將TextInput放到ScrollView中再設置本屬性)
"never"(默認值),點擊TextInput以外的子組件會使當前的軟鍵盤收起。此時子元素不會收到點擊事件。
"always",鍵盤不會自動收起,ScrollView也不會捕捉點擊事件,但子組件可以捕獲。
"handled",當點擊事件被子組件捕獲時,鍵盤不會自動收起。這樣切換TextInput時鍵盤可以保持狀態(tài)。多數(shù)帶有TextInput的情況下你應該選擇此項。
false,已過期,請使用"never"代替。
true,已過期,請使用"always"代替。
解決方案:看如下例子
- ReactNative本地圖片如何獲取其base64編碼?(一般指采用//按鈕點擊事件注意收起鍵盤 _checkAndSubmit = () => { Keyboard.dismiss(); }; ...
關鍵是要獲取到本地圖片的uri,用到了Image.resolveAssetSource方法,ImageEditor.cropImage方法和ImageStore.getBase64ForTag方法,具體可以查詢官方文檔
解決方案:看如下代碼
import item from "../../images/avator_upload_icon.png"; const info = Image.resolveAssetSource(item); ImageEditor.cropImage(info.uri, { size: { width: 126, height: 126 }, resizeMode: "cover" }, uri => { ImageStore.getBase64ForTag(uri, base64ImageData => { // 獲取圖片字節(jié)碼的base64字符串 this.setState({ avatarBase64: base64ImageData }); }, err => { console.warn("ImageStoreError" + JSON.stringify(err)); }); }, err => { console.warn("ImageEditorError" + JSON.stringify(err)); });- ReactNative如何讀取iOS沙盒里的圖片?
解決方案:看如下代碼
let RNFS = require("react-native-fs");- ReactNative如何做到圖片寬度不變,寬高保持比例,高度自動調整。 RN圖片均需要指定寬高才會顯示,如果圖片數(shù)據(jù)的寬高不定,但又希望寬度保持不變、不同圖片的高度根據(jù)比例動態(tài)變化,就需要用到下面這個庫,業(yè)務場景常用于文章、商品詳情的多圖展示。
解決方案:使用react-native-scalable-image
- navigor 無法使用的解決辦法從0.44版本開始,Navigator被從react native的核心組件庫中剝離到了一個名為react-native-deprecated-custom-components的多帶帶模塊中。如果你需要繼續(xù)使用Navigator,則需要先npm i facebookarchive/react-native-custom-components安裝,然后從這個模塊中import,即import { Navigator } from "react-native-deprecated-custom-components"
如果報錯如下參考下面的解決方案
React-Native – undefined is not an object (“evaluating _react3.default.PropTypes.shape”)
解決方案:
如果已經(jīng)安裝了,先卸載npm uninstall --save react-native-deprecated-custom-components
用下面的命令安裝
npm install --save https://github.com/facebookarchive/react-native-custom-components.git在我們使用Navigator的js文件中加入下面這個導入包就可以了。
import { Navigator } from"react-native-deprecated-custom-components";(注意最后有一個分號)
就可以正常使用Navigator組件了。
- ReactNative開發(fā)的APP啟動閃白屏問題由于處理JS需要時間,APP啟動會出現(xiàn)一閃而過白屏,可以通過啟動頁延遲加載方法來避免這類白屏,可以用下面的庫
- ReactNative如何做到無感熱更新
解決方案:react-native-splash-screen無論是整包熱更新還是差量熱更新,均需要最終替換JSBundle等文件來完成更新過程,實現(xiàn)原理是js來控制啟動頁的消失時間,等原生把bundle包下載(或合并成新bundle包)解壓到目錄以后,通知js消失啟動頁,由于熱更新時間一般很短,建議使用差量熱更新,一秒左右,所以用戶等啟動頁消失后看到的就是最新的版本。
解決方案(以整包更新為例):原生端完成更新及刷新操作,注意里面的 [_bridge reload]
//前往更新js包 RCT_EXPORT_METHOD(gotoUpdateJS:(NSString *)jsUrl andResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){ if (!jsUrl) { return; } //jsbundle更新采用靜默更新 //更新 NSLog(@"jsbundleUrl is : %@", jsUrl); [[LJFileHelper shared] downloadFileWithURLString:jsUrl finish:^(NSInteger status, id data) { if(status == 1){ NSLog(@"下載完成"); NSError *error; NSString *filePath = (NSString *)data; NSString *desPath = [NSString stringWithFormat:@"%@",NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]]; [SSZipArchive unzipFileAtPath:filePath toDestination:desPath overwrite:YES password:nil error:&error]; if(!error){ [_bridge reload]; resolve([NSNumber numberWithBool:true]); NSLog(@"解壓成功"); }else{ resolve([NSNumber numberWithBool:false]); NSLog(@"解壓失敗"); } } }]; reject = nil; }JS端
// 原生端通過回調結果通知JS熱更新情況,JS端 UpdateModule.gotoUpdateJS(jsUrl).then(resp => { if ( resp ) { // 成功更新通知隱藏啟動頁 DeviceEventEmitter.emit("hide_loading_page","hide"); } else { // 出問題也要隱藏啟動頁,用戶繼續(xù)使用舊版本 DeviceEventEmitter.emit("hide_loading_page","hide"); // 其他處理 } });啟動頁消失,用戶看到的是新版APP
async componentWillMount() { this.subscription = DeviceEventEmitter.addListener("hide_loading_page", this.hideLoadingPage); appUpdateModule.updateJs(); } hideLoadingPage = ()=> { SplashScreen.hide(); };注意做好容錯,例如弱網(wǎng)無網(wǎng)環(huán)境下的處理,熱更新失敗下次保證再次熱更新的處理,熱更新時間把控,超過時間下次再reload,是否將熱更新reload權利交給用戶等等都可以擴展。
- ReactNative如何取消部分警告debug模式下調試經(jīng)常會有黃色的警告,有些警告可能是短時間不需要處理,通過下面的解決方法能忽略部分警告提示
解決方案:使用console.ignoredYellowBox
import { AppRegistry } from "react-native"; import "./app/Common/SetTheme" import "./app/Common/Global" import App from "./App"; console.ignoredYellowBox = ["Warning: BackAndroid is deprecated. Please use BackHandler instead.", "source.uri should not be an empty string","Remote debugger is in a background tab which", "Setting a timer", "Encountered two children with the same key,", "Attempt to read an array index", ]; AppRegistry.registerComponent("ReactNativeTemplate", () => App);- ReactNative開發(fā)遇到android網(wǎng)絡圖片顯示不出來的問題開發(fā)過程中有時會遇到iOS圖片正常顯示,但是安卓卻只能顯示部分網(wǎng)絡圖片,造成這個的原因有多種,參考下面的解決方案。
解決方案:
安卓增加resizeMethod屬性并設置為resize
resizeMethod官方解釋
resizeMethod enum("auto", "resize", "scale") 當圖片實際尺寸和容器樣式尺寸不一致時,決定以怎樣的策略來調整圖片的尺寸。默認值為auto。 auto:使用啟發(fā)式算法來在resize和scale中自動決定。 resize: 在圖片解碼之前,使用軟件算法對其在內存中的數(shù)據(jù)進行修改。當圖片尺寸比容器尺寸大得多時,應該優(yōu)先使用此選項。 scale:對圖片進行縮放。和resize相比, scale速度更快(一般有硬件加速),而且圖片質量更優(yōu)。在圖片尺寸比容器尺寸小或者只是稍大一點時,應該優(yōu)先使用此選項。 關于resize和scale的詳細說明請參考http://frescolib.org/docs/resizing-rotating.html.如果是FlatList或ScrollView等包裹圖片,嘗試設置
removeClippedSubviews={true}//ios set false
如果還是有問題,嘗試配合react-native-image-progress
還可以謹慎嘗試使用react-native-fast-image
- ReactNative判斷及監(jiān)控網(wǎng)絡情況方法總結提前獲取用戶的網(wǎng)絡情況很有必要,RN主要靠NetInfo來獲取網(wǎng)絡狀態(tài),不過隨著RN版本的更新也有一些變化。
解決方案:較新的RN版本(大概是0.50及以上版本)
this.queryConfig(); queryConfig = ()=> { this.listener = NetInfo.addEventListener("connectionChange", this._netChange); }; // 網(wǎng)絡發(fā)生變化時 _netChange = async(info)=> { const { type, //effectiveType } = info; const netCanUse = !(type === "none" || type === "unknown" || type === "UNKNOWN" || type === "NONE"); if (!netCanUse) { this.setState({ isNetError : true }); this.alertNetError(); //或者其他通知形式 } else { try { // 注意這里的await語句,其所在的函數(shù)必須有async關鍵字聲明 let response = await fetch(CONFIG_URL); let responseJson = await response.json(); const configData = responseJson.result; if (response && configData) { this.setState({ is_show_tip: configData.is_show_tip, app_bg: CONFIG_HOST + configData.app_bg, jumpUrl: configData.url, isGetConfigData: true }, () => { SplashScreen.hide(); }) } else { // 錯誤碼也去殼 if ( responseJson.code === 400 ) { this.setState({ isGetConfigData: true }, () => { SplashScreen.hide(); }) } else { this.setState({ isGetConfigData: false }, () => { SplashScreen.hide(); }) } } } catch (error) { console.log("queryConfig error:" + error); this.setState({ isGetConfigData: true }, () => { SplashScreen.hide(); }) } } }; alertNetError = () => { setTimeout(()=> { SplashScreen.hide(); }, 1000); if ( ! this.state.is_show_tip && this.state.isGetConfigData ) { return } else { Alert.alert( "NetworkDisconnected", "", [ {text: "NetworkDisconnected_OK", onPress: () => { this.checkNetState(); }}, ], {cancelable: false} ); } }; checkNetState = () => { NetInfo.isConnected.fetch().done((isConnected) => { if ( !isConnected ) { this.alertNetError(); } else { this.queryConfig(); } }); };老版本
async componentWillMount() { this.queryConfig(); } checkNetState = () => { NetInfo.isConnected.fetch().done((isConnected) => { console.log("111Then, is " + (isConnected ? "online" : "offline")); if (!isConnected) { this.alertNetError(); } else { this.queryConfig(); } }); }; alertNetError = () => { setTimeout(()=> { SplashScreen.hide(); }, 1000); console.log("111111"); if (!this.state.is_show_tip && this.state.isGetConfigData) { console.log("222222"); return } else { console.log("33333"); Alert.alert( "NetworkDisconnected", "", [ { text: "NetworkDisconnected_OK", onPress: () => { this.checkNetState(); } }, ], {cancelable: false} ); } }; queryConfig = ()=> { NetInfo.isConnected.addEventListener( "connectionChange", this._netChange ); }; // 網(wǎng)絡發(fā)生變化時 _netChange = async(isConnected)=> { console.log("Then, is " + (isConnected ? "online" : "offline")); if (!isConnected) { console.log("666"); this.setState({ isNetError: true }); this.alertNetError(); } else { try { // 注意這里的await語句,其所在的函數(shù)必須有async關鍵字聲明 let response = await fetch(CONFIG_URL); let responseJson = await response.json(); const configData = responseJson.result; if (response && configData) { this.setState({ is_show_tip: configData.is_show_tip, app_bg: CONFIG_HOST + configData.app_bg, jumpUrl: configData.url, isGetConfigData: true }, () => { SplashScreen.hide(); this.componentNext(); }) } else { this.setState({ isGetConfigData: false }, () => { SplashScreen.hide(); this.componentNext(); }) } } catch (error) { console.log("queryConfig error:" + error); this.setState({ isGetConfigData: true }, () => { SplashScreen.hide(); this.componentNext(); }) } } };- ReactNative版本升級后報錯有廢棄代碼的快速解決方法使用第三方庫或者老版本升級時會遇到報錯提示某些方法被廢棄,這時候尋找和替換要花不少時間,而且還容易漏掉。
解決方案:
根據(jù)報錯信息,搜索廢棄的代碼,例如報錯提示:Use viewPropTypes instead of View.propTypes.
搜索命令:grep -r "View.propTypes" .
替換搜索出來的代碼即可。
這是用于查找項目里的錯誤或者被廢棄的代碼的好方法
- 解決ReactNative的TextInput在0.55中文無法輸入的問題此問題主要體現(xiàn)在iOS中文輸入法無法輸入漢字,是0.55版RN的一個bug
解決方案:使用下面的MyTextInput替換原TextInput
import React from "react"; import { TextInput as Input } from "react-native"; export default class MyTextInput extends React.Component { static defaultProps = { onFocus: () => { }, }; constructor(props) { super(props); this.state = { value: this.props.value, refresh: false, }; } shouldComponentUpdate(nextProps, nextState) { if (this.state.value !== nextState.value) { return false; } return true; } componentDidUpdate(prevProps) { if (prevProps.value !== this.props.value && this.props.value === "") { this.setState({ value: "", refresh: true }, () => this.setState({ refresh: false })); } } focus = (e) => { this.input.focus(); }; onFocus = (e) => { this.input.focus(); this.props.onFocus(); }; render() { if (this.state.refresh) { return null; } return ( { this.input = ref; }} value={this.state.value} onFocus={this.onFocus} /> ); } }ReactNative集成第三方DEMO編譯時遇到RCTSRWebSocket錯誤的解決方法報錯信息如下
Ignoring return value of function declared with warn_unused_result attribute解決方案:
StackOverFlow上的解決方法:
在navigator雙擊RCTWebSocket project,移除build settings > custom compiler 下的flags版權聲明:
轉載時請注明作者Kovli以及本文地址:
http://www.kovli.com/2018/06/...
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100782.html
閱讀 1278·2021-10-11 10:57
閱讀 2051·2021-09-02 15:15
閱讀 1613·2019-08-30 15:56
閱讀 1205·2019-08-30 15:55
閱讀 1163·2019-08-30 15:44
閱讀 985·2019-08-29 12:20
閱讀 1331·2019-08-29 11:12
閱讀 1073·2019-08-28 18:29