經過第一部分開發 React Native APP —— 從改造官方 Demo 開始(一)介紹,App 框架基本構建完成,這部分主要討論 UI/互動、App 釋出前的準備工作及如何釋出,具體內容包括:
- 在使用 react navigation 的前提下,iOS 實現單個頁面從下往上(modal)的進入動畫
- 尺寸自適應
- 設定啟動頁,更換桌面圖示、app 展示名稱、appID
- 打包釋出
一 擴充套件 react navigation
這裡的擴充套件指的是實現可單獨配置頁面的進入方式(react navigation 預設只支援全域性配置,要麼 card
,要麼 modal
,配置後所有頁面進入動畫相同)。
實現上述效果需要做兩方面修改:StackNavigator
API(在 route.js
中使用)和進入某個頁面是的呼叫方式。
1.1 修改 StackNavigator
API
修改後如果使頁面預設狀態為 card,只需要輸入對應頁面即可,比如 ..navigate('ScreenSome1')
;如果要使某個頁面進入方式為 modal 只需要在路徑上加上 Modal 比如:..navigate('ScreenSome2Modal')
。
需要注意的是如果頁面進入方式為 modal,需要自定義 header,因為預設 header 樣式失效,都疊在一塊了。
/**
* route.js
* 自定義 StackNavigator,可以選擇 screen 進入方式
*/
const StackModalNavigator = (routeConfigs, navigatorConfig) => {
const CardStackNavigator = StackNavigator(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 = StackNavigator(
{
CardStackNavigator: { screen: CardStackNavigator },
...modalRouteConfig
},
{
// 如果頁面進入方式為 modal,需要自定義 header(預設 header 樣式失效,都疊在一塊了)
mode: "modal",
headerMode: "none"
}
);
return ModalStackNavigator;
};
// 設定路由
const AppNavigator = StackModalNavigator();
複製程式碼
1.2 頁面中呼叫
首先我們新建頁面 ScreenSome2
,接下來就讓它以 modal 的形式進入(從螢幕下面進入),作為對比 ScreenSome1
以 card
的形式進入(預設進入方式,從螢幕右側進入)。
因為以 modal 形式進入的頁面需要自定義 header,一般只是一個關閉按鈕,以 ScreenSome2
為例:
/**
* ScreenSome2/view.js
* 自定義 header(關閉按鈕)
*/
<View>
{/* TouchableHighlight 為關閉按鈕的熱區 */}
<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
程式碼,這裡是 ScreenHome
頁面中的程式碼:
/**
* ScreenHome/view.js
* 自定義 header(關閉按鈕)
*/
{
/* ScreenSome2 從螢幕右側進入 */
}
<Button
title="goSome1"
onPress={() => self.navigation.navigate("ScreenSome1")}
/>;
{
/* ScreenSome2 從螢幕下面進入 */
}
<Button
title="goSome2Modal"
onPress={() => self.navigation.navigate("ScreenSome2Modal")}
/>;
複製程式碼
最終效果圖:
二 自適應
自適應主要包括兩方面:尺寸根據螢幕大小自適應,包括 fontSize
,width
等;圖片解析度根據螢幕解析度自適應,也就常說的二倍圖、三倍圖等。
2.1 尺寸自適應
尺寸自適應的原理是通過獲取手機螢幕的寬度,尺寸做相應比例的調整,為此封裝了一個工具函式,放在了 config/pxToDp.js
中。
調整後的目錄如下:
config/pxToDp.js
尺寸轉換的工具函式
尺寸轉換的工具函式在第一部分開發 React Native APP —— 從改造官方 Demo 開始(一)已經新增
1)編寫自適應尺寸工具函式
因為所有涉及尺寸的資料都要轉換(fontSize
,width
等),所以對轉換後的資料要做處理,保證:1.大於等於 1 的數字向上取整;2.小於 1 的數字,如果是 ios 平臺統一設為 0.5;如果是安卓平臺統一設為 1(因為安卓平臺解析度千差萬別萬別,低解析度的螢幕顯示 0.5 的尺寸會有鋸齒狀)。工具函式完整程式碼如下:
/**
* pxToDp.js
* 自適應佈局
* @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
!有點坑,這個工具函式還有待優化。
2)使用自適應尺寸工具函式
使用方法很簡單,在需要轉換單位的元件中將轉換尺寸的工具函式引入,將需要轉換的尺寸傳入工具函式即可,以 ScreenHome
為例:
/**
* ScreenHome/view.js
*/
// 引入尺寸轉換工具函式
import pxToDp from "../../config/pxToDp";
// 將需要轉換的單位傳入 pxToDp 中
<Text style={{ fontSize: pxToDp(36) }}>home</Text>;
複製程式碼
2.3 圖片解析度自適應
手機解析度越來越多,尤其安卓,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) }}
/>
複製程式碼
最終效果圖如下:
- iphone6: 2 倍圖(圖片放大後可以看到裡面有
2X
字樣) - iphone7Plus:3 倍圖(圖片放大後可以看到裡面有
3X
字樣) - Nexus4:2 倍圖
- Pixel2:3 倍圖
三 修改桌面圖示、App 展示名稱,設定啟動頁
修改桌面圖示、App 展示名稱相對簡單,設定啟動頁稍微麻煩。另外,iOS 修改桌面圖示、App 展示名稱,設定啟動頁都需要在 Xcode 中進行。
3.1 設定桌面圖示
因為 App 圖示對應多個尺寸,手動改寫太麻煩,這個網站可以自動生成 MakeAppIcon。
並不是所有尺寸的圖片都需要,見下文。
- iOS
準確點講不能叫設定桌面圖示,而應該是 App 圖示,因為我們需要設定的不止有桌面展示的圖示,還有設定時 app 圖示、訊息推送時 app 圖示,此外如果要釋出到 App store,還需要設定 Apple Store 展示用的 App 圖示。
1)圖片準備
以上不同地方用到的 app 圖示尺寸各不相同,具體如下(只針對 iphone,不包括 ipad,iwatch):
尺寸 | 名稱 | 用途 | 是否必須 |
---|---|---|---|
120x120 | Icon-60@2x.png | 桌面圖示 (2x) | 必須 |
180x180 | Icon-60@3x.png | 桌面圖示 (3x) | 可選,但推薦設定 |
80x80 | Icon-40@2x.png | Spotlight 圖示 (2x) | 可選,但推薦設定 |
120x120 | Icon-40@3x.png | Spotlight 圖示 (3x) | 可選,但推薦設定 |
58x58 | Icon-29@2x.png | 設定圖示 (2x) | 可選,但推薦設定 |
87x87 | Icon-29@3x.png | 設定圖示 (3x) | 可選,但推薦設定 |
40x40 | Icon-20@2x.png | 通知圖示 (3x) | 可選,但推薦設定 |
80x80 | Icon-20@3x.png | 通知圖示 (3x) | 可選,但推薦設定 |
1024x1024 | iTunesArtwork@2x.png | App Store (2x) | 必須 |
名稱不是說一定要和上面相同,但
Icon
、尺寸(如60
)還有倍率(@nx)要有,型別為png
。
2)將圖片拖放至 Xcode 指定位置,具體是:Project Navigator -> Images.xcassets -> AppIcon
,如下圖
拖放完成後,通過檔案管理器檢視專案目錄,也會發現相應圖片。
- 安卓
安卓的 app 圖示相對簡單,只需要設定桌面圖示。設定位置在 yourApp/android/app/src/main/res/
目錄下,這個目錄預設有四個資料夾,裡面各對應放置了一種尺寸的桌面圖示圖片,圖片尺寸不同,但名稱相同,統一為 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 的螢幕很少了,那麼這個資料夾就可以不要
3.2 修改 App 展示名稱
- iOS
調出工程設定選單(雙擊工程名稱或者單擊然後然後右側選擇 Targets --> yourProject),進入 info
選項,在 Custom iOS Target Properties
中新增 Bundle display name
,其 value
便是 App 的名稱。具體設定如下圖:
- 安卓
安卓修改 App 展示名稱在這個檔案中 yourApp/android/app/src/main/res/values/strings.xml
。
strings.xml
這個檔案很簡單,全部內容如下:
<resources>
<string name="app_name">你的app名稱</string>
</resources>
複製程式碼
替換 你的app名稱
為你想要的名字就好。
NOTE:
安卓的話,還要修改預設包名(applicationId
),如果不修改,如果系統監測到當前應用的 applicationId
和已安裝的某個應用相同而簽名不同,會報錯:“簽名不一致 該應用可能已被惡意篡改”。
在這個檔案中修改包名: yourApp/android/app/build.gradle
:
// ...
defaultConfig {
applicationId "com.yourAppId"
// ...
}
// ...
複製程式碼
3.3 設定啟動頁
這裡使用了第三方外掛react-native-splash-screen,官網教程已經很詳細,這裡做簡要介紹。
- 專案中安裝依賴
1)下載依賴
yarn add react-native-splash-screen
複製程式碼
2)新增到專案中
react-native link react-native-splash-screen
複製程式碼
3)在 React Native 配置
這裡指的是設定啟動頁什麼時候消失,下面的程式碼是首頁載入完 5s 後啟動頁消失。
/**
* ScreenHome/index.js
* 設定啟動頁消失時間
*/
import SplashScreen from "react-native-splash-screen"; // 引入 react-native-splash-screen
export default class ScreenHome extends Component {
// ...other code
componentDidMount() {
// 隱藏啟動頁,如果不設定消失時間,在元件載入完啟動頁自動隱藏
setTimeout(() => {
SplashScreen.hide();
}, 5000);
}
// ...other code
}
複製程式碼
- iOS 設定
1)更新 AppDelegate.m
:
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "SplashScreen.h" // 匯入啟動頁外掛
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...其他程式碼
[self.window makeKeyAndVisible];
[SplashScreen show]; // 顯示啟動頁
return YES;
}
@end
複製程式碼
2)準備啟動頁圖片
檔案必須是 png 格式的圖片,命名需對應尺寸,可參考下面的命名:
如需自動生成可使用這個網頁:appicon
尺寸 | 名稱 | 用途 | 是否必須 |
---|---|---|---|
640 x 960 | Default@2x.png | iPhone 4 | 非必須,推薦設定 |
640 x 1136 | Default-568h@2x.png | iPhone 5 | 非必須,推薦設定 |
750 x 1334 | Default-667h@2x.png | iPhone 6, 豎屏 | 必須(必須有至少一個啟動頁圖片) |
1242 x 2208 | Default-736h@3x.png | iPhone 6 Plus, 豎屏 | 非必須,推薦設定 |
2208 x 1242 | Default-Landscape-736h@3x.png | iPhone 6 Plus, 橫屏 | 非必須,推薦設定 |
1125 × 2436 | Default-812h@3x.png | iPhone X, 豎屏 | 非必須,推薦設定 |
2436 x 1125 | Default-Landscape-812h@3x.png | iPhone X, 橫屏 | 非必須,推薦設定 |
NOTE:
- 很多教程給出的啟動頁尺寸比上面的要多,有可能是 Xcode 版本不同導致,Xcode 9.2,iOS 7+ 只需要上面七個尺寸;
- 名稱並非一定要按照上面的要求,直接使用
Default尺寸x尺寸.png
也可以;
3)在 Xcode 中設定啟動頁
首先新建 LaunchImage
檔案,操作步驟如下:
然後在 general
設定中將啟動頁指向剛才新建的 LaunchImage
檔案,注意 Launch Screen File 必須為空,不然就指向 LaunchScreen.xib 中預設的啟動頁了:
- 安卓配置
1)更新 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 張圖片就好。
4)優化啟動頁出現前的短暫白屏
到這裡,啟動頁功能已經 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 安裝配置完成後出現閃退,參考下面設定:
在 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>
複製程式碼
因為 react-native-splash-screen 需要一個名為 primary_dark
的顏色值作為狀態列的顏色。
3.4 最終效果圖
設定完桌面圖示、修改 APP 展示名稱及設定啟動頁之後的效果圖如下:
四 打包釋出
4.1 安卓打包釋出
- 生成簽名
keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
複製程式碼
如果使用 mac,執行該命令的目錄隨意。但一定要保管好自己的 my-release-key.keystore
檔案,如果忘記簽名,不能在原有 App 上面升級,只能重新打包釋出。同時,不要將 keystore
檔案放入版本控制中。
- 設定 gradle 變數
1)首先將簽名檔案 my-release-key.keystore
放在目錄 yourApp/android/app/
下
2)修改檔案 yourApp/android/gradle.properties
新增下面程式碼 (替換 *****
為正確的 keystore 密碼、別名、和 key 密碼):
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'
,做下述修改:
在 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
複製程式碼
如果還有其他問題,可參考下這篇文章:安卓打包釋出那些坑
4.2 iOS 打包釋出
iOS 打包釋出有些麻煩,對於大多數非 iOS 開發者的限制不是 React Native 本身,而是蘋果本身的機制,比如,必須要有蘋果開發者賬號。iOS 打包釋出打算另寫文章。如果要了解發布流程,可以參考這兩篇文章:iOS 釋出 App Store 詳細圖文教程,React Native iOS 詳細打包步驟
注意一點:打包時
--entry-file
安卓、iOS 是同一個入口檔案index.js
,不在區分安卓/iOS
五 小結
到目前位置,從改造官方 demo 開始,一個比較完整的 React Native App 完成了,在此基礎上可以不斷擴充套件完善。
當然,從生產角度來說,這個 demo 的完成度不高,比如,很多樣式還是最原始的狀態、比如 WebView(App 中嵌入 H5)、下拉重新整理等也沒有涉及。其中 WebView、下拉重新整理等常用功能會逐步整合到這個 demo 中,但樣式並不打算做過多優化,因為從使用角度來講,樣式的完成度越高意味著可定製性越差,並且,那樣也會導致程式碼的可讀性變差。希望這個 demo 可以成為完整、普適但不臃腫的腳手架。
不過,同一套程式碼,安卓和 iOS 上展示的樣式會有不同,針對這個,會寫文章單獨說明。
參考資料
Choose transition mode for each screen in StackNavigator
React Native 開發適配心得
Apple Developer - App Icon
在模擬器安卓 4.0 上執行正常,在手機上安卓 6.0 7.0 都閃退 不知道什麼情況求解
Issues with resources generated by react in Android Studio 3
React Native 的預設單位和自適應佈局方案