正確實踐Jetpack SplashScreen API —— 在所有Android系統上使用總結,內含原理分析
theme: arknights highlight: androidstudio
- 小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。
- 本文已參與 「掘力星計劃」 ,贏取創作大禮包,挑戰創作激勵金。
1.前言
文章末尾有演示的APK連結,感興趣的同學,可以自行下載體驗一下
官方Android 12的Splash Screen文件地址
官方Splash Screen相容庫,支援所有版本系統
本篇文章主要圍繞下面三個問題來介紹: - 我們能從Android 12 SplashScreen API裡面學到什麼? - 新出的SplashScreen相容庫又是什麼?能做成什麼樣子? - 小甲同學:我想看Android12 SplashScreen原始碼,可以嗎?
前方高能預警:一定要記得『點贊❤️+關注❤️+收藏❤️』起來,划走了可就再也找不到了😅😅🙈🙈
進入正題,我們先介紹:SplashScreen如何使用,以及目前會遇到的問題,如何無縫過渡?會出現什麼問題,怎麼解決?
2.SplashScreen使用
首先我們需要把compileSdk和targetSdk(可選)
升級到31
2.1.Android12版本
(A).主題和外觀配置
```xml
(B).延長啟動畫面
kotlin
val content: View = findViewById(android.R.id.content)
content.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// 模擬一些資料的初始化,再取消掛起
return if (viewModel.isReady) {
// 取消掛起,恢復頁面內容繪製
content.viewTreeObserver.removeOnPreDrawListener(this)
true
} else {
// 掛起,內容還沒有準備好
false
}
}
}
)
(C).關閉啟畫面的動畫
kotlin
// 自己定製關閉的動畫
splashScreen.setOnExitAnimationListener { splashScreenView ->
val slideUp = ObjectAnimator.ofFloat(
// 你們自己控制,自己隨便寫什麼動畫,這裡我們測試讓圖示移動
splashScreenView.iconView,
View.TRANSLATION_Y,
0f,
-splashScreenView.height.toFloat()
)
slideUp.interpolator = AnticipateInterpolator()
slideUp.duration = 200L
slideUp.doOnEnd { splashScreenView.remove() }
slideUp.start()
}
(D).遇到的問題
- android:windowSplashScreenBrandingImage 定義的圖片尺寸要求是多少?總覺得有點拉伸;
- 使用AnimationDrawable 或者 AnimatedVectorDrawable,來設定中心圖示,
會出現“中心圖示”消失的情況
,靜態圖示不會有這種問題出現; - Android12父主題設定
android:windowBackground
被覆蓋,看不到效果
問題1: 在原始碼裡面也沒有看到具體的值或者比例大小,怎麼辦呢?
小技巧: 使用一個超大的正方形的圖示設定進去測試了一下,拉伸不要緊,我們要的是比例, 然後測量了一下比例為:2.5 : 1,所以設計品牌名圖示的時候,可以設定為400 * 160這樣的比例為2.5:1的圖示
問題2: 針對中心圖示會閃現消失的問題做測試,
測試一:靜態Icon、測試二:動態Icon
靜態中心圖示 - 正常
下面我們來測試動態中心圖示,為了方便測試出效果,我們覆蓋住圖示後面的背景色,方便對比,最後發現:測試結果不太理想,效果不行
```xml
動態中心圖示,不正常
仔細看圖示後面的「藍色背景」
我們再來看一下官方文件中的順滑效果
官方效果順滑
對比官方的效果,猜測可能是模擬器和預覽版Android12的問題,主要是沒有真機來測試Android12這個效果,不過這難不到我們,如果你的模擬器也有同樣問題
,請跟著我們做如下操作:
下面我們使用AnimatedVectorDrawable來製作動態圖示,
為了觀察出效果:我們開啟模擬器的開發者選項,找到Animator時長縮放設定為:動畫時長x10
,來往下看效果:
10倍慢放 - 看著才正常
笑臉眼睛動畫的向量圖檔案 👇👇,點選檢視線上製作向量圖動畫 ```xml
``
後來我們又用了AnimationDrawable測試了一下慢放效果也不行,你仔細想一下:圖片輪播放效果能好嗎?
所以:
AnimationDrawable不推薦`,我們這裡推薦使用:AnimatedVectorDrawable為向量圖新增動畫效果
問題3: Android12父主題設定android:windowBackground
被覆蓋,看不到效果
不要緊,只要我們的UI設計師(美工)
按照如下尺寸規範來設計,使用靜態中心圖示,一樣可以實現同樣效果:
中心圖示: 圖示內容區域內邊距2/3,防止元素被切
品牌名圖示: 設計的尺寸比例為:2.5:1
2.2.SplashScreen相容庫
(A).依賴庫
點選檢視Core庫裡面的最新版本
gradle
// 可在所有Android版本上使用的相容庫
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
(B).主題和外觀配置
- 定義Activity應該使用的主題
```xml
``` - 建立父主題給啟動畫面使用
```xml
``` - AndroidManifest.xml配置Activity的主題
xml
<manifest>
<application android:theme="@style/Theme.App.Starting">
<!-- application和activity,兩個選一個配置: @style/Theme.App.Starting -->
<activity android:theme="@style/Theme.App.Starting">
...
(C).初始化SplashScreen
java
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val splashScreen = installSplashScreen()
setContent { ...... }
splashScreen.setKeepVisibleCondition {
!mainViewModel.mockDataLoading()
}
splashScreen.setOnExitAnimationListener(this)
}
(D).中心圖示大小修改
xml
<item name="splashScreenIconSize">@dimen/....</item>
(E).遇到的問題
相容庫目前存在的問題
- 沒有android:windowSplashScreenBrandingImage
這個屬性
- 配置了中心圖示,會裁剪成圓形
- 低版本系統不配置windowSplashScreenAnimatedIcon
會出現預設的Icon
- Android12父主題設定android:windowBackground
被覆蓋,看不到效果
問題1: 是因為相容庫的layout檔案目錄下面的splash_screen_view.xml沒有“品牌名的檢視”
,大家點選檢視一下,兩個佈局的xml內容就知道了:
frameworks下面的Android12的splash_screen_view.xml
core-splashscreen下面的相容庫的splash_screen_view.xml
但是我們在Android12即values-v31的themes.xml裡面依然可以配置android:windowSplashScreenBrandingImage
這個屬性,因為Android12的SplashScreen是整合在frameworks裡面的;
問題2: 是因為相容庫裡面使用了MaskedDrawable包裝了Icon,會裁剪成圓形,圖示內容設計要保留2/3的內邊距,否則會出現內容被裁剪掉的問題;
如何修復這個裁剪圓形問題呢?
把原始碼拷貝出來,總共就3個原始碼檔案,自己複製出來修改刪除也可以的
或者,圖示設計準則為:內容保留內邊距為2/3,防止元素被裁剪
問題3: 寫一個透明的drawable.xml然後替換就行了,類似如下方式
```xml
``
**問題4:** Android12**父主題**設定
android:windowBackground`被覆蓋,看不到效果
不要緊,只要我們的UI設計師(美工)
按照如下尺寸規範來設計,使用靜態中心圖示,一樣可以實現同樣效果:
中心圖示: 圖示內容區域內邊距2/3,防止元素被切
品牌名圖示: 設計的尺寸比例為:2.5:1
(F).製作一個啟動頁
- 1.模仿快手App的啟動頁
只需要配置父主題的
android:windowBackground
Android5.0 ~ Android11 效果
由於我們在文章上面介紹到Android12上,無法為SplashScreen設定父主題的android:windowBackground
,但我們依然可以通過配置靜態中心圖示來做到一樣的效果的,請看下面的效果:
Android12 效果
如果你的UI設計師,給你向量圖,那麼你就可以讓中心圖示在Android12系統上動起來了😆
另外,可以建議UI設計師:統一所有系統上,啟動頁“中心”圖示,居中展示,不然會有點怪
- 2.動態圖示啟動頁
如果設計成動態啟動圖示,這個需要考慮2個因素:
一、 低版本系統表現效果不一致,有些系統裡面,動態圖示只在啟動頁關閉的時候才顯示(
親測Android平板5.1.1系統就是這樣的
);
二、 如何相容低版本系統,是先展示底部品牌名,最後只能等動態圖示顯示咯?
如果喜歡折騰的同學,可以多測試測試,我在使用Android10.0測試動態圖示的時候,效果看著還可以,具體系統下限在哪?
這個看你們產品設計定位了,還有測試妹妹是否同意你們用,效果是否符合你們的產品;
建議的做法是:
- Android 5.0 ~ Android 11.0系統,都統一使用android:windowBackground
配置啟動頁背景
- Android12.0 如果UI設計師給你做了向量圖,你可以做動態的中心圖示,不給你,使用靜態圖示也可以的,參考上面:模擬快手App啟動頁
為了在模擬器上能正常顯示出效果,我們在模擬器的開發者選項,找到Animator時長縮放設定為:動畫時長x10
,放慢10倍,缺真機測試😂😂😂😂😂
Android12 動態啟動頁圖示
3.原始碼分析
我們這裡只分析Android12 SplashScreen,相容庫沒有太多內容不足500行,感興趣的同學可以自己閱讀一下
我們在XXXActivity
裡面第一次用到了splashScreen.setOnExitAnimationListener
,從這裡開始往源頭開始找,在下面的方法初始化了SplashScreen
java
//android.app.Activity
private SplashScreen getOrCreateSplashScreen() {
synchronized (this) {
if (mSplashScreen == null) {
mSplashScreen = new SplashScreen.SplashScreenImpl(this);
}
return mSplashScreen;
}
}
我們來看SplashScreenImpl實現類
```java //android.app.Activity
class SplashScreenImpl implements SplashScreen { ...... //把SplashScreenImpl新增到這個單例類裡面 private final SplashScreenManagerGlobal mGlobal; public SplashScreenImpl(Context context) { mGlobal = SplashScreenManagerGlobal.getInstance(); } @Override public void setOnExitAnimationListener(@NonNull SplashScreen.OnExitAnimationListener listener) { ...... mGlobal.addImpl(this); // 用於後面執行啟動畫面將退出的回撥 } ...... public void setSplashScreenTheme(@StyleRes int themeId) { ...... try { //設定啟動畫面的主題 AppGlobals.getPackageManager().setSplashScreenTheme(......); } catch (RemoteException e) { Log.w(TAG, "Couldn't persist the starting theme", e); } } }
// 啟動畫面管理器
class SplashScreenManagerGlobal {
......
// 管理多個閃屏實現
private final ArrayList
private SplashScreenManagerGlobal() {
// 向此程序註冊啟動畫面管理器
ActivityThread.currentActivityThread().registerSplashScreenManager(this);
}
......
private static final Singleton<SplashScreenManagerGlobal> sInstance =
new Singleton<SplashScreenManagerGlobal>() {
@Override
protected SplashScreenManagerGlobal create() {
return new SplashScreenManagerGlobal();
}
};
private void addImpl(SplashScreenImpl impl) {
synchronized (mGlobalLock) {
mImpls.add(impl);
}
}
private void removeImpl(SplashScreenImpl impl) {
synchronized (mGlobalLock) {
mImpls.remove(impl);
}
}
......
public void handOverSplashScreenView(IBinder token,SplashScreenView splashScreenView) {
//呼叫的是 => splashScreenView.transferSurface();
transferSurface(splashScreenView);
//回撥 => impl.mExitAnimationListener.onSplashScreenExit(view);
dispatchOnExitAnimation(token, splashScreenView);
}
......
}
``
我們看到初始化SplashScreenManagerGlobal的時候,
向此程序註冊啟動畫面管理器`
```java //android.app.ActivityThread
public void registerSplashScreenManager(SplashScreen.SplashScreenManagerGlobal manager) {
synchronized (this) {
mSplashScreenGlobal = manager;
}
}
如何把SplashScreen新增到當前的視窗的呢?
ActivityThread繼承ClientTransactionHandler,裡面有一個這樣的抽象方法:
java
//android.app.ClientTransactionHandler
public abstract void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
@NonNull SplashScreenViewParcelable parcelable);
```
ActivityThread肯定會實現這個方法,那麼是誰呼叫了它呢?由於篇幅問題,就不一行一行程式碼的去介紹分析了,感興趣的同學,可以自己深入研究,我們下面貼出來呼叫的流程圖
誰呼叫了handleAttachSplashScreenView(),它的呼叫鏈流程圖如下:
好了,看完流程圖,我們再看一下ActivityThread#handleAttachSplashScreenView
```java //android.app.ActivityThread
@Override public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r, @Nullable SplashScreenView.SplashScreenViewParcelable parcelable) { final DecorView decorView = (DecorView) r.window.peekDecorView(); if (parcelable != null && decorView != null) { createSplashScreen(r, decorView, parcelable); } ...... }
private void createSplashScreen(ActivityClientRecord r, DecorView decorView, SplashScreenView.SplashScreenViewParcelable parcelable) { // 初始化SplashScreenView構建器 final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity); // 從parcelable中獲取配置資料,並通過build()初始化SplashScreenView,設定資料 final SplashScreenView view = builder.createFromParcel(parcelable).build(); // 把SplashScreenView新增到DecorView中 decorView.addView(view); // 設定SystemUI顏色 view.attachHostActivityAndSetSystemUIColors(r.activity, r.window); // 重新整理檢視 view.requestLayout(); ...... } ``` 核心的部分原始碼已經分析差不多了,剩下的一些原始碼,感興趣的同學可以自己檢視閱讀
4.總結
- compileSdk升級到31
- 產品中統一使用相容庫SplashScreen
gradle
implementation 'androidx.core:core-splashscreen:最新版本'
- 演示示例中資源目錄
text
drawable —— 定義低版本的drawable資源
drawable-v23 —— 定義6.0以上的資源
drawable-v31 —— 定義Android12及以上的資源
values —— 定義預設資源
values-night —— 定義預設深色模式資源
values-v23 —— 定義6.0以上系統資源
values-v31 —— 定義Android12及以上的資源
values-night-v31 —— 定義Android12及以上的深色模式資源
- 啟動頁圖示設計準則
中心圖示大圖,內容需要保留2/3的內邊距,否則圖示會被裁剪掉,另外:圖示尺寸大小可以修改;
底部品牌名圖示:尺寸比例需要為 2.5:1
相容庫SplashScreen低版本不支援設定底部品牌圖示,
Android12需要在values-v31目錄手動新增如下屬性,才可以顯示品牌名圖示;
```xml
``
- **Android12以下系統**可以使用
android:windowBackground為父主題設定視窗背景,**切記不要**在Android12及以上系統設定父主題的視窗背景
(因為沒有效果😅😅)`
- Android12系統以下系統,使用
android:windowBackground
的話,一定要給windowSplashScreenAnimatedIcon
設定一個透明的drawable,否則會出現機器人圖示 - windowSplashScreenBackground 這個屬性的顏色一定要注意,配置有問題的話,啟動頁過渡到主頁面的時候,會有這個顏色閃出來,建議和Activity的
android:windowBackground
配置成一樣的顏色 - 在啟動畫面上面,新增一個
“廣告或者推廣頁面”
,程式碼和效果如下:
kotlin
override fun onSplashScreenExit(splashScreenViewProvider: SplashScreenViewProvider) {
if(splashScreenViewProvider.view is ViewGroup){
// 在這裡新增一個啟動頁廣告或者推廣頁面
val composeView = ComposeView(this@SplashScreenCompatActivity).apply {
setContent {
SplashAdScreen(onCloseAd = {
splashScreenViewProvider.remove()
})
}
}
(splashScreenViewProvider.view as ViewGroup).addView(composeView)
return
}
}
實踐 - 啟動頁新增廣告或者推廣頁
參考地址
- 線上流程圖製作
- 官方文件 Splash screens
- Google官網介紹向量圖
- 線上svg編輯器
- 線上製作向量圖動畫
- 線上合併多個GIF製作
文章中示例的演示APK及原始碼地址:
| 靜態圖示啟動頁 | 動態圖示啟動頁(Android12系統有動畫效果)
| 啟動頁加廣告 |
| :---: | :---: | :---: |
| 下載:SplashScreen快手啟動頁效果的apk001 | 下載:SplashScreen快手啟動頁效果的apk002| 下載:SplashScreen啟動頁廣告apk|
| 提取碼:7gj2 | 提取碼:b6ce | 提取碼:fnva |
- 鴻蒙ArkUI如何開發跨平臺應用?
- HarmonyOS玩轉ArkUI動效 - 水母動畫
- Compose挑燈夜看 - 照亮手機螢幕裡面的書本內容
- 順手修復了Jetpack Compose官方文件中的一個多點觸控示例的Bug
- 正確實踐Jetpack SplashScreen API —— 在所有Android系統上使用總結,內含原理分析
- Jetpack Compose處理“導航欄、狀態列、鍵盤” 影響內容顯示的問題集錦
- 閒聊Android懸浮的“系統文字選擇選單”和“ActionMode解析”——附上原理分析
- Jetpack Compose實現bringToFront功能——附上原理分析
- Android跨程序傳大圖思考及實現——附上原理分析