fix(splash): 修复华为设备启动动画兼容性问题

- 添加 core-splashscreen 依赖支持 Android 12+ SplashScreen API
- 配置原生 Splash 主题消除冷启动白屏
- 添加 values-v31 和 values-night-v31 适配 Android 12+
- 更新 launch_background.xml 显示品牌 Logo
- MainActivity 添加 installSplashScreen() 处理
- 视频转码为 H.264 格式解决华为 HEVC 解码器兼容问题
- 添加视频初始化调试日志

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-14 19:54:40 -08:00
parent 2e44263834
commit 7bfd6822a7
11 changed files with 137 additions and 34 deletions

View File

@ -43,6 +43,11 @@ fun getAutoVersionName(): String {
return "$flutterVersionName.$autoVersionCode"
}
dependencies {
// SplashScreen API for Android 12+ compatibility
implementation("androidx.core:core-splashscreen:1.0.1")
}
android {
namespace = "com.rwadurian.rwa_android_app"
compileSdk = flutter.compileSdkVersion

View File

@ -3,7 +3,9 @@ package com.rwadurian.rwa_android_app
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.core.content.FileProvider
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
@ -13,6 +15,22 @@ class MainActivity : FlutterActivity() {
private val INSTALLER_CHANNEL = "com.rwadurian.app/apk_installer"
private val MARKET_CHANNEL = "com.rwadurian.app/app_market"
override fun onCreate(savedInstanceState: Bundle?) {
// Install splash screen - must be called before super.onCreate()
val splashScreen = installSplashScreen()
// Keep splash screen visible until Flutter is ready
var keepSplashScreen = true
splashScreen.setKeepOnScreenCondition { keepSplashScreen }
super.onCreate(savedInstanceState)
// Dismiss splash screen after a short delay to ensure smooth transition
window.decorView.postDelayed({
keepSplashScreen = false
}, 300)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<!-- Launch background with brand color and logo (API 21+) -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- Background color matching splash theme -->
<item android:drawable="@color/splash_background" />
<!-- You can insert your own image assets here -->
<!-- <item>
<!-- Centered app icon -->
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<!-- Launch background with brand color and logo -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- Background color matching splash theme -->
<item android:drawable="@color/splash_background" />
<!-- You can insert your own image assets here -->
<!-- <item>
<!-- Centered app icon -->
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- SplashScreen theme for dark mode (Android 12+ API 31+) -->
<style name="LaunchTheme" parent="Theme.SplashScreen">
<!-- Splash screen background color (same as light mode for brand consistency) -->
<item name="windowSplashScreenBackground">#FFF5E6</item>
<!-- Splash screen icon -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<!-- Icon animation duration (max 1000ms) -->
<item name="windowSplashScreenAnimationDuration">800</item>
<!-- Post-splash theme -->
<item name="postSplashScreenTheme">@style/NormalTheme</item>
<!-- Remove the branding image at bottom (Android 12+) -->
<item name="android:windowSplashScreenBrandingImage">@null</item>
</style>
<!-- Normal theme after splash (dark mode) -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<!-- SplashScreen theme for dark mode (Android 11 and below) -->
<style name="LaunchTheme" parent="Theme.SplashScreen">
<!-- Splash screen background color (same as light mode for brand consistency) -->
<item name="windowSplashScreenBackground">#FFF5E6</item>
<!-- Splash screen icon -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<!-- Post-splash theme -->
<item name="postSplashScreenTheme">@style/NormalTheme</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<!-- Normal theme after splash (dark mode) -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- SplashScreen theme for Android 12+ (API 31+) -->
<style name="LaunchTheme" parent="Theme.SplashScreen">
<!-- Splash screen background color -->
<item name="windowSplashScreenBackground">#FFF5E6</item>
<!-- Splash screen icon -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<!-- Icon animation duration (max 1000ms) -->
<item name="windowSplashScreenAnimationDuration">800</item>
<!-- Post-splash theme -->
<item name="postSplashScreenTheme">@style/NormalTheme</item>
<!-- Remove the branding image at bottom (Android 12+) -->
<item name="android:windowSplashScreenBrandingImage">@null</item>
</style>
<!-- Normal theme after splash -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
<!-- Splash screen background color (cream/light orange) -->
<color name="splash_background">#FFF5E6</color>
</resources>

View File

@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<!-- SplashScreen theme for Android 11 and below -->
<style name="LaunchTheme" parent="Theme.SplashScreen">
<!-- Splash screen background color -->
<item name="windowSplashScreenBackground">#FFF5E6</item>
<!-- Splash screen icon (uses app icon) -->
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
<!-- Post-splash theme -->
<item name="postSplashScreenTheme">@style/NormalTheme</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<!-- Normal theme after splash -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>

View File

@ -44,11 +44,30 @@ class _SplashPageState extends ConsumerState<SplashPage> {
///
Future<void> _initializeVideo() async {
debugPrint('[SplashPage] ========== 开始初始化视频 ==========');
debugPrint('[SplashPage] 视频路径: assets/videos/splash.mp4');
try {
// assets
debugPrint('[SplashPage] 创建 VideoPlayerController...');
_videoController = VideoPlayerController.asset('assets/videos/splash.mp4');
debugPrint('[SplashPage] 开始 initialize()...');
final stopwatch = Stopwatch()..start();
await _videoController!.initialize();
stopwatch.stop();
debugPrint('[SplashPage] initialize() 完成,耗时: ${stopwatch.elapsedMilliseconds}ms');
//
final value = _videoController!.value;
debugPrint('[SplashPage] 视频信息:');
debugPrint('[SplashPage] - 尺寸: ${value.size.width} x ${value.size.height}');
debugPrint('[SplashPage] - 时长: ${value.duration.inMilliseconds}ms');
debugPrint('[SplashPage] - 是否已初始化: ${value.isInitialized}');
debugPrint('[SplashPage] - 是否有错误: ${value.hasError}');
if (value.hasError) {
debugPrint('[SplashPage] - 错误信息: ${value.errorDescription}');
}
if (mounted) {
setState(() {
@ -59,7 +78,9 @@ class _SplashPageState extends ConsumerState<SplashPage> {
_videoController!.addListener(_onVideoStateChanged);
//
debugPrint('[SplashPage] 开始播放视频...');
await _videoController!.play();
debugPrint('[SplashPage] play() 调用完成isPlaying: ${_videoController!.value.isPlaying}');
// 1
Future.delayed(const Duration(seconds: 1), () {
@ -70,8 +91,23 @@ class _SplashPageState extends ConsumerState<SplashPage> {
}
});
}
} catch (e) {
debugPrint('[SplashPage] 视频初始化失败: $e');
} catch (e, stackTrace) {
debugPrint('[SplashPage] ========== 视频初始化失败 ==========');
debugPrint('[SplashPage] 错误类型: ${e.runtimeType}');
debugPrint('[SplashPage] 错误信息: $e');
debugPrint('[SplashPage] 堆栈跟踪:\n$stackTrace');
// controller
if (_videoController != null) {
final value = _videoController!.value;
debugPrint('[SplashPage] Controller 状态:');
debugPrint('[SplashPage] - 是否已初始化: ${value.isInitialized}');
debugPrint('[SplashPage] - 是否有错误: ${value.hasError}');
if (value.hasError) {
debugPrint('[SplashPage] - 错误描述: ${value.errorDescription}');
}
}
//
_navigateToNextPage();
}