diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index 5acd121bd69..2b18cde3570 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -598,6 +598,9 @@
Allows an application to write to the user dictionary.
+
+ The background color used for the root window. Default is [code]black[/code].
+
If [code]true[/code], this makes the navigation and status bars translucent and allows the application content to extend edge to edge.
[b]Note:[/b] You should ensure that none of the application content is occluded by system elements by using the [method DisplayServer.get_display_safe_area] and [method DisplayServer.get_display_cutouts] methods.
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 3e33706f372..8a650e4bb49 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1070,6 +1070,8 @@ void EditorExportPlatformAndroid::_fix_themes_xml(const Ref
main_theme_attributes["android:windowIsTranslucent"] = bool_to_string(transparency_allowed);
if (transparency_allowed) {
main_theme_attributes["android:windowBackground"] = "@android:color/transparent";
+ } else {
+ main_theme_attributes["android:windowBackground"] = "#" + p_preset->get("screen/background_color").operator Color().to_html(false);
}
Dictionary splash_theme_attributes;
@@ -2179,6 +2181,7 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/edge_to_edge"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "screen/background_color", PROPERTY_HINT_COLOR_NO_ALPHA), Color()));
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data_backup/allow"), false));
@@ -3099,6 +3102,13 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Refget("screen/background_color").operator Color().to_html(false);
+ if (_is_transparency_allowed(p_preset) && p_preset->get("gradle_build/use_gradle_build")) {
+ background_color = "#00000000";
+ }
+ command_line_strings.push_back("--background_color");
+ command_line_strings.push_back(background_color);
+
bool debug_opengl = p_preset->get("graphics/opengl_debug");
if (debug_opengl) {
command_line_strings.push_back("--debug_opengl");
diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java
index d83741ddb62..dac80282726 100644
--- a/platform/android/java/app/src/com/godot/game/GodotApp.java
+++ b/platform/android/java/app/src/com/godot/game/GodotApp.java
@@ -56,11 +56,12 @@ public class GodotApp extends GodotActivity {
}
}
- private final Runnable updateImmersiveAndEdgeToEdgeModes = () -> {
+ private final Runnable updateWindowAppearance = () -> {
Godot godot = getGodot();
if (godot != null) {
godot.enableImmersiveMode(godot.isInImmersiveMode(), true);
godot.enableEdgeToEdge(godot.isInEdgeToEdgeMode(), true);
+ godot.setSystemBarsAppearance();
}
};
@@ -74,12 +75,12 @@ public class GodotApp extends GodotActivity {
@Override
public void onResume() {
super.onResume();
- updateImmersiveAndEdgeToEdgeModes.run();
+ updateWindowAppearance.run();
}
@Override
public void onGodotMainLoopStarted() {
super.onGodotMainLoopStarted();
- runOnUiThread(updateImmersiveAndEdgeToEdgeModes);
+ runOnUiThread(updateWindowAppearance);
}
}
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
index 622433db063..934c4827ae6 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt
@@ -324,12 +324,13 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
}
}
- private fun updateImmersiveAndEdgeToEdgeModes() {
+ private fun updateWindowAppearance() {
val editorWindowInfo = getEditorWindowInfo()
if (editorWindowInfo == EDITOR_MAIN_INFO || editorWindowInfo == RUN_GAME_INFO) {
godot?.apply {
enableImmersiveMode(isInImmersiveMode(), true)
enableEdgeToEdge(isInEdgeToEdgeMode(), true)
+ setSystemBarsAppearance()
}
}
}
@@ -339,13 +340,13 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
runOnUiThread {
// Hide the loading indicator
editorLoadingIndicator?.visibility = View.GONE
- updateImmersiveAndEdgeToEdgeModes()
+ updateWindowAppearance()
}
}
override fun onResume() {
super.onResume()
- updateImmersiveAndEdgeToEdgeModes()
+ updateWindowAppearance()
if (getEditorWindowInfo() == EDITOR_MAIN_INFO &&
godot?.isEditorHint() == true &&
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 55480677136..3311e7733de 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -38,6 +38,7 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
import android.hardware.Sensor
import android.hardware.SensorManager
import android.os.*
@@ -47,6 +48,8 @@ import android.view.*
import android.widget.FrameLayout
import androidx.annotation.Keep
import androidx.annotation.StringRes
+import androidx.core.graphics.ColorUtils
+import androidx.core.graphics.toColorInt
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsAnimationCompat
@@ -187,6 +190,7 @@ class Godot private constructor(val context: Context) {
private val isEdgeToEdge = AtomicBoolean(false)
private var useDebugOpengl = false
private var darkMode = false
+ private var backgroundColor: Int = Color.BLACK
internal var containerLayout: FrameLayout? = null
var renderView: GodotRenderView? = null
@@ -255,6 +259,17 @@ class Godot private constructor(val context: Context) {
} else if (commandLine[i] == "--fullscreen") {
useImmersive.set(true)
newArgs.add(commandLine[i])
+ } else if (commandLine[i] == "--background_color") {
+ val colorStr = commandLine[i + 1]
+ try {
+ backgroundColor = colorStr.toColorInt()
+ Log.d(TAG, "background color = $backgroundColor")
+ } catch (e: java.lang.IllegalArgumentException) {
+ Log.d(TAG, "Failed to parse background color: $colorStr")
+ }
+ runOnHostThread {
+ getActivity()?.window?.decorView?.setBackgroundColor(backgroundColor)
+ }
} else if (commandLine[i] == "--use_apk_expansion") {
useApkExpansion = true
} else if (hasExtra && commandLine[i] == "--apk_expansion_md5") {
@@ -459,6 +474,24 @@ class Godot private constructor(val context: Context) {
@Keep
fun isInEdgeToEdgeMode() = isEdgeToEdge.get()
+ fun setSystemBarsAppearance() {
+ val window = getActivity()?.window ?: return
+ val isLight = ColorUtils.calculateLuminance(getWindowBackgroundColor(window)) > 0.5
+
+ val controller = WindowInsetsControllerCompat(window, window.decorView)
+ controller.isAppearanceLightNavigationBars = isLight
+ controller.isAppearanceLightStatusBars = isLight
+ }
+
+ private fun getWindowBackgroundColor(window: Window): Int {
+ val background = window.decorView.background
+ return if (background is ColorDrawable) {
+ background.color
+ } else {
+ backgroundColor
+ }
+ }
+
/**
* Used to complete initialization of the view used by the engine for rendering.
*