仍是摘自L小庸的文章,加入了一點我的的實踐和理解html
通過第一部分開發 React Native APP —— 從改造官方 Demo 開始(1)介紹,App 框架基本構建完成,這部分主要關注 UI/交互、App 發佈前的準備工做及如何發佈,具體內容包括:java
這部分在android下無效…… ,我的是win,因此後面沒有展現效果圖,有mac的能夠嘗試下效果react
官方標註:modal - Make the screens slide in from the bottom which is a common iOS pattern. Only works on iOS, has no effect on Android.
這裏的擴展指的是實現可單獨配置頁面的進入方式(react navigation 默認只支持全局配置,要麼 card
,要麼 modal
,配置後全部頁面進入動畫相同)。android
實現上述效果須要作兩方面修改:createStackNavigator
API(在 route.js
中使用)和進入某個頁面是的調用方式。
ios
..navigate('ScreenSome1')
;若是要使某個頁面進入方式爲 modal 只須要在路徑上加上 Modal 好比:
..navigate('ScreenSome2Modal')
。
須要注意的是 若是頁面進入方式爲 modal,須要自定義 header,由於默認 header 樣式失效,都疊在一塊了。(尚未實際遇到,先mark着)
關於card和modal,我的還並非很理解,後續理解了再補上
首先咱們新建頁面 ScreenSome2
,接下來就讓它以 modal 的形式進入(從屏幕下面進入),做爲對比 ScreenSome1
以 card
的形式進入(默認進入方式,從屏幕右側進入)。git
由於以 modal 形式進入的頁面須要自定義 header,通常只是一個關閉按鈕,以 ScreenSome2
爲例:github
/**
* ScreenSome2/view.js
* 自定義 header(關閉按鈕)
*/
import React from 'react';
import { TouchableHighlight,Text,View } from 'react-native'
import pxToDp from '../../config/pxToDp';
<View>
<TouchableHighlight
onPress={() => self.navigation.goBack()}
underlayColor="transparent"
style={{
display: "flex",
justifyContent: "center",
marginTop: pxToDp(30),
width: pxToDp(150),
height: pxToDp(90),
backgroundColor: "yellow"
}}
>
<Text style={{ marginLeft: pxToDp(24) }}>關閉</Text>
</TouchableHighlight>
<Text style={{ fontSize: pxToDp(36) }}>some2,以 modal 的形式進入</Text>
</View>複製代碼
ScreenSome2
代碼:
/**
* ScreenSome2/view.js
* 自定義 header(關閉按鈕)
*/
import React from 'react';
import { TouchableHighlight,Text,View } from 'react-native'
import pxToDp from '../../config/pxToDp';
export default self => (
<View>
<TouchableHighlight
onPress={() => self.navigation.goBack()}
underlayColor="transparent"
style={{
display: "flex",
justifyContent: "center",
marginTop: pxToDp(30),
width: pxToDp(150),
height: pxToDp(90),
backgroundColor: "yellow"
}}
>
<Text style={{ marginLeft: pxToDp(24) }}>關閉</Text>
</TouchableHighlight>
<Text style={{ fontSize: pxToDp(36) }}>some2,以 modal 的形式進入</Text>
</View>
);複製代碼
爲了看起來稍微好看點,加了一點樣式,不加也能夠。style.jsweb
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
buttonContainer: {
margin: 20
},
});複製代碼
別忘了在route裏配置新的路由,完整代碼以下:react-native
// 引入依賴
import React from 'react';
import { createStackNavigator, createAppContainer } from 'react-navigation'
// 引入頁面組件
import ScreenBottomTab from '../screens/ScreenBottomTab';
import ScreenHome from '../screens/ScreenHome';
import ScreenSome1 from '../screens/ScreenSome1';
import ScreenSome2 from '../screens/ScreenSome2';
import ScreenTab1 from '../screens/ScreenTab1';
import ScreenTab2 from '../screens/ScreenTab2';
import ScreenTab3 from '../screens/ScreenTab3';
/**
* 自定義 StackNavigator,能夠選擇 screen 進入方式
* 默認狀態爲 card,只須要輸入對應頁面,好比 ..navigate('ScreenSome1')
* 若是要使某個頁面進入方式爲 modal 只須要在路徑上加上 Modal
* 好比:..navigate('ScreenSome2Modal')
*/
const StackModalNavigator = (routeConfigs, navigatorConfig) => {
const CardStackNavigator = createStackNavigator(routeConfigs, navigatorConfig);
const modalRouteConfig = {};
const routeNames = Object.keys(routeConfigs);
for (let i = 0; i < routeNames.length; i++) {
modalRouteConfig[`${routeNames[i]}Modal`] = routeConfigs[routeNames[i]];
}
const ModalStackNavigator = createStackNavigator(
{
CardStackNavigator: { screen: CardStackNavigator },
...modalRouteConfig,
},
{
// 若是頁面進入方式爲 modal,須要自定義 header(默認 header 樣式失效,都疊在一塊了)
mode: 'modal',
headerMode: 'none',
},
);
return ModalStackNavigator;
};
// 配置路由
const AppNavigator = StackModalNavigator({
ScreenBottomTab: ScreenBottomTab,
//下面幾個配置的是測試Navigator不一樣使用場景用,只須要tab的話,只要ScreenBottomTab: ScreenBottomTab便可
ScreenHome: ScreenHome,
ScreenSome1: ScreenSome1,
ScreenSome2: ScreenSome2,
ScreenTab1: ScreenTab1,
ScreenTab2: ScreenTab2,
ScreenTab3: ScreenTab3,
});
const App = createAppContainer(AppNavigator)
export default App
複製代碼
this.props.navigation有不少方法,demo中(ScreenHome,ScreenSome1)只用到.navigate,.push,.goBack,具體可參考https://reactnavigation.org/docs/zh-Hans/navigation-prop.html
自適應主要包括兩方面:尺寸根據屏幕大小自適應,包括 fontSize
,width
等;圖片分辨率根據屏幕分辨率自適應,也就常說的二倍圖、三倍圖等。bash
copy自@L小庸的代碼片斷
尺寸自適應的原理是經過獲取手機屏幕的寬度,尺寸作相應比例的調整,爲此封裝了一個工具函數,放在了 config/pxToDp.js
中。
config/pxToDp.js
尺寸轉換的工具函數1)編寫自適應尺寸工具函數
由於全部涉及尺寸的數據都要轉換(fontSize
,width
等),因此對轉換後的數據要作處理,保證:1.大於等於 1 的數字向上取整;2.小於 1 的數字,若是是 ios 平臺統一設爲 0.5;若是是安卓平臺統一設爲 1(由於安卓平臺分辨率千差萬別萬別,低分辨率的屏幕顯示 0.5 的尺寸會有鋸齒狀)。工具函數完整代碼以下:
/**
* 自適應佈局
* @param uiElementPx: ui給的原始尺寸
*/
import { Dimensions, Platform } from 'react-native';
// app 只有豎屏模式,因此能夠只獲取一次 width
const deviceWidthDp = Dimensions.get('window').width;
// UI 默認給圖是 750
const uiWidthPx = 750;
function pxToDp(uiElementPx) {
const transferNumb = uiElementPx * deviceWidthDp / uiWidthPx;
if (transferNumb >= 1) {
// 避免出現循環小數
return Math.ceil(transferNumb);
} else if (Platform.OS === 'android') {
// 若是是安卓,最小爲1,避免邊框出現鋸齒
return 1;
}
return 0.5;
}
export default pxToDp;
複製代碼
實際上,經過Dimensions.get('window').width
獲取的屏幕寬度和本身想象的可能有出入,好比,iphone7 屏幕 4.7'',獲取到的寬度是375
,華爲 P9 是 5.2',但獲取到的寬度倒是是360
!有點坑,這個工具函數還有待優化。我的尚未遇到這麼多場景,也尚未更好的方案,若是踩坑,後續更新。
本身折騰了半天flex佈局,相比web的flex佈局,仍是有不少區別,須要的能夠參考練習練習~也能夠本身找難度更高的佈局進行練習,參考代碼https://github.com/elainema/ELAINE/blob/master/RN/AwesomeProject/src/screens/demos/FlexTest.js
手機分辨率愈來愈多,尤爲安卓,React Native 能夠根據不一樣分辨率加載不一樣尺寸的圖片,只需在圖片命名上面加以區分。
好比咱們有張圖片叫 test.png
,尺寸爲 40 x 40
(單位像素),爲了作到自適應屏幕分辨率,咱們還須要提供它的 2 倍圖,3 倍圖,這樣,一張圖片就對應 3 個尺寸,以下:
# 一張圖片提供 3個尺寸
test.png # 尺寸 40 x 40
test@2x.png # 尺寸 80 x 80
test@3x.png # 尺寸 120 x 120複製代碼
name@nx
是 n (n > 1) 倍圖命名規範,React Native 也是根據命名判斷圖片尺寸的。
在引用圖片的時候直接使用 不加倍率後綴的圖片名,好比,直接使用 test.png
,以下:
/**
* ScreenTab3/view.js
*/
<Image
source={require("../../assets/images/test.png")}
style={{ height: pxToDp(80), width: pxToDp(80) }}
/>複製代碼
修改桌面圖標、App 展現名稱相對簡單,設置啓動頁稍微麻煩。
IOS的設置並無mac去嘗試,可參考@L小庸的https://juejin.im/post/5a9602c45188257a7262e3fb嘗試,
踩坑: 開發要連真機調試,尤爲是涉及原生功能的部分,切記。
由於 App 圖標對應多個尺寸,手動改寫太麻煩,這個網站能夠自動生成 MakeAppIcon。
並非全部尺寸的圖片都須要,見下文。
安卓的 app 圖標相對簡單,只須要設置桌面圖標。設置位置在 yourApp/android/app/src/main/res/
目錄下,
這個目錄默認有5個文件夾,裏面各對應放置了一種尺寸的桌面圖標圖片,圖片尺寸不一樣,但名稱相同,統一爲 ic_launcher.png
,具體以下所示:
文件夾名稱 | 含義 | 文件夾內部圖片尺寸 | 文件夾內部圖片名稱 |
---|---|---|---|
mipmap-ldpi | Low Density Screen | 36x36 | ic_launcher.png |
mipmap-mdpi | Medium Density Screen | 48x48 | ic_launcher.png |
mipmap-hdpi | High Density Screen | 72x72 | ic_launcher.png |
mipmap-xhdpi | Extra-high density screen | 96x96 | ic_launcher.png |
mipmap-xxhdpi | xx-high density screen | 144x144 | ic_launcher.png |
mipmap-xxxhdpi | xxx-high density screen | 192x192 | ic_launcher.png |
若是你使用了 MakeAppIcon 的服務,直接將對應文件夾所有放入 res/
目錄下就好,否則就手動替換圖標。
能夠根據實際需求刪除沒必要要的文件,好比,120 DPI 的屏幕不多了,那麼這個文件夾就能夠不要
IOS的參考@L小庸的https://juejin.im/post/5a9602c45188257a7262e3fb嘗試
安卓修改 App 展現名稱在這個文件中 yourApp/android/app/src/main/res/values/strings.xml
。
strings.xml
這個文件很簡單,所有內容以下:
<resources>
<string name="app_name">你的app名稱</string>
</resources>
複製代碼複製代碼
替換 你的app名稱
爲你想要的名字就好。
安卓的話,還要修改默認包名(applicationId
),若是不修改,若是系統監測到當前應用的applicationId
和已安裝的某個應用相同而簽名不一樣,會報錯:「簽名不一致 該應用可能已被惡意篡改」。
在這個文件中修改包名: yourApp/android/app/build.gradle
:
// ...
defaultConfig {
applicationId "com.yourAppId"
// ...
}
// ...複製代碼
這裏使用了第三方插件react-native-splash-screen,官網教程已經很詳細,這裏作簡要介紹。
1)下載依賴
yarn add react-native-splash-screen複製代碼
2)添加到項目中
react-native link react-native-splash-screen複製代碼
3)在 React Native 配置
這裏指的是設置啓動頁何時消失,下面的代碼是首頁加載完 5s 後啓動頁消失。
export default class ScreenHome extends Component {
// ...other code
componentDidMount() {
// 隱藏啓動頁,若是不設置消失時間,在組件加載完啓動頁自動隱藏
setTimeout(() => {
SplashScreen.hide();
}, 5000);
}
// ...other code
}複製代碼
1)更新 MainActivity.java(yourApp/android/app/src/main/java/com/yourApp/MainActivity.java)
:
import android.os.Bundle; // here
import com.facebook.react.ReactActivity;
// react-native-splash-screen >= 0.3.1
import org.devio.rn.splashscreen.SplashScreen; // here
// react-native-splash-screen < 0.3.1
import com.cboy.rn.splashscreen.SplashScreen; // here
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this); // here
super.onCreate(savedInstanceState);
}
// ...other code
}複製代碼
2)新建 launch_screen.xml
在 app/src/main/res/layout
中建立 launch_screen.xml
(若是沒有 layout
目錄,新建),內容以下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/launch_screen">
</LinearLayout>複製代碼
3)準備不一樣尺寸的啓動頁圖片並放到項目中
安卓是經過文件夾路徑尋找啓動頁面的,因此,多張尺寸的啓動頁名稱相同,都爲 launch_screen.png
,但要放在不一樣文件夾中,文件夾放置目錄爲 yourApp/android/app/src/main/res/
,名稱及對應放置的圖片尺寸以下:
文件夾名稱 | 含義 | 文件夾內部圖片尺寸 | 文件夾內部圖片名稱 |
---|---|---|---|
drawable-ldpi | Low Density Screen | 240x320 | launch_screen.png |
drawable-mdpi | Medium Density Screen | 320x480 | launch_screen.png |
drawable-hdpi | High Density Screen | 480x800 | launch_screen.png |
drawable-xhdpi | Extra-high density screen | 720x1280 | launch_screen.png |
drawable-xxhdpi | xx-high density screen | 960x1600 | launch_screen.png |
drawable-xxxhdpi | xxx-high density screen | 1280x1920 | launch_screen.png |
480x800
起步放置 4 張圖片就好。
文件必須是 png 格式的圖片,開始隨便找了一張圖片,結果打包各類報錯,命名需對應尺寸
4)優化啓動頁出現前的短暫白屏(copy,踩坑填坑)
到這裏,啓動頁功能已經 ok,但若是仔細看,能夠看到啓動頁出現前會有短暫白屏,此時可經過更改android/app/src/main/res/values/styles.xml
解決:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<!--設置透明背景-->
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>複製代碼
這種方案實際沒有根本解決問題:會發現這樣設置之後點擊圖片不能當即彈出應用,而有短暫的等待時間,待填坑。
5)解決安卓 6.0,7.0 安裝配置完成後出現閃退,參考下面設置:(copy,踩坑填坑)
在 android/app/src/main/res/values
下面新建 colors.xml
文件,內容以下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- this is referenced by react-native-splash-screen and will throw an error if not defined. its value does nothing, just here to avoid a runtime error. -->
<color name="primary_dark">#000000</color>
</resources>複製代碼
primary_dark
的顏色值做爲狀態欄的顏色。
設置完桌面圖標、修改 APP 展現名稱及設置啓動頁以後的效果圖以下:
demo裏後續添加了本身練習的部分,因此效果圖和代碼稍有差異,須要的只用care文章裏的步驟就好
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
1)首先將簽名文件 my-release-key.keystore
放在目錄 yourApp/android/app/
下
2)修改文件 yourApp/android/gradle.properties
添加下面代碼 (替換 *****
爲正確的 keystore 密碼、別名、和 key 密碼):
注意這裏的 MYAPP_RELEASE_STORE_PASSWORD和 MYAPP_RELEASE_KEY_PASSWORD必定要和生成簽名時的密碼保持一致
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=*****
MYAPP_RELEASE_KEY_PASSWORD=*****複製代碼
3)添加簽名信息到 app 的 gradle 配置中
編輯文件 yourApp/android/app/build.gradle
加入簽名信息
android {
...
defaultConfig { ... }
signingConfigs {
release {
if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
storeFile file(MYAPP_RELEASE_STORE_FILE)
storePassword MYAPP_RELEASE_STORE_PASSWORD
keyAlias MYAPP_RELEASE_KEY_ALIAS
keyPassword MYAPP_RELEASE_KEY_PASSWORD
}
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release
}
}
}複製代碼
在終端輸入下面命令
cd android && ./gradlew assembleRelease複製代碼
等待構建完成,即可以在 yourApp/android/app/build/outputs/apk/release/app-release.apk
中找到編譯後的發佈版本。
NOTE:若是遇到這個錯誤:
Execution failed for task ':app:processReleaseResources'
,作下述修改:,我的沒有遇到,mark先~
在 yourApp/android/gradle.properties
文件最後添加下面代碼:
classpath 'com.android.tools.build:gradle:3.0.0'
distributionUrl=https://services.gradle.org/distributions/gradle-4.1-all.zip
android.enableAapt2=false複製代碼
NOTE:若是遇到Execution failed for task ':app:bundleReleaseJsAndAssets'.不幸的遇到了這個錯…………
刪除 android/app/build
and android/build
folders and run react-native run-android
again
到目前爲止,從改造官方 demo 開始,一個比較完整的 React Native App 完成了,在此基礎上能夠不斷擴展完善。
後面若是有有空會在各部分加入更多詳細的demo和使用場景,以及踩坑分享。
固然,從生產角度來講,這個 demo 的完成度不高,好比,不少樣式仍是最原始的狀態、好比 WebView(App 中嵌入 H5)、下拉刷新等也沒有涉及。其中 WebView、下拉刷新等經常使用功能會逐步集成到這個 demo 中,但樣式並不打算作過多優化,由於從使用角度來說,樣式的完成度越高意味着可定製性越差,而且,那樣也會致使代碼的可讀性變差。但願這個 demo 能夠成爲完整、普適但不臃腫的腳手架。