Fix transparency background issue on Android

Fixes https://github.com/godotengine/godot/issues/106703
This commit is contained in:
Fredia Huya-Kouadio 2025-05-21 21:57:09 -07:00
parent e45cc68092
commit 547450befd
11 changed files with 61 additions and 14 deletions

View File

@ -2544,7 +2544,7 @@
Display server supports [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url], which is commonly used for inputting Chinese/Japanese/Korean text. This is handled by the operating system, rather than by Godot. [b]Windows, macOS, Linux (X11)[/b]
</constant>
<constant name="FEATURE_WINDOW_TRANSPARENCY" value="11" enum="Feature">
Display server supports windows can use per-pixel transparency to make windows behind them partially or fully visible. [b]Windows, macOS, Linux (X11/Wayland)[/b]
Display server supports windows can use per-pixel transparency to make windows behind them partially or fully visible. [b]Windows, macOS, Linux (X11/Wayland), Android[/b]
</constant>
<constant name="FEATURE_HIDPI" value="12" enum="Feature">
Display server supports querying the operating system's display scale factor. This allows automatically detecting the hiDPI display [i]reliably[/i], instead of guessing based on the screen resolution and the display's reported DPI (which might be unreliable due to broken monitor EDID). [b]Windows, Linux (Wayland), macOS[/b]
@ -3079,7 +3079,7 @@
<constant name="WINDOW_FLAG_TRANSPARENT" value="3" enum="WindowFlags">
The window background can be transparent.
[b]Note:[/b] This flag has no effect if [method is_window_transparency_available] returns [code]false[/code].
[b]Note:[/b] Transparency support is implemented on Linux (X11/Wayland), macOS, and Windows, but availability might vary depending on GPU driver, display manager, and compositor capabilities.
[b]Note:[/b] Transparency support is implemented on Android, Linux (X11/Wayland), macOS, and Windows, but availability might vary depending on GPU driver, display manager, and compositor capabilities.
</constant>
<constant name="WINDOW_FLAG_NO_FOCUS" value="4" enum="WindowFlags">
The window can't be focused. No-focus window will ignore all input, except mouse clicks.

View File

@ -76,7 +76,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
case FEATURE_NATIVE_DIALOG_FILE_MIME:
//case FEATURE_NATIVE_ICON:
//case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_WINDOW_TRANSPARENCY:
case FEATURE_CLIPBOARD:
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_ORIENTATION:
@ -592,7 +592,13 @@ void DisplayServerAndroid::window_set_flag(DisplayServer::WindowFlags p_flag, bo
}
bool DisplayServerAndroid::window_get_flag(DisplayServer::WindowFlags p_flag, DisplayServer::WindowID p_window) const {
switch (p_flag) {
case WindowFlags::WINDOW_FLAG_TRANSPARENT:
return is_window_transparency_available();
default:
return false;
}
}
void DisplayServerAndroid::window_request_attention(DisplayServer::WindowID p_window) {
@ -961,3 +967,7 @@ void DisplayServerAndroid::set_native_icon(const String &p_filename) {
void DisplayServerAndroid::set_icon(const Ref<Image> &p_icon) {
// NOT SUPPORTED
}
bool DisplayServerAndroid::is_window_transparency_available() const {
return GLOBAL_GET_CACHED(bool, "display/window/per_pixel_transparency/allowed");
}

View File

@ -256,6 +256,8 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
virtual bool is_window_transparency_available() const override;
DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
~DisplayServerAndroid();
};

View File

@ -1038,6 +1038,10 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
store_string_at_path(manifest_path, manifest_text);
}
bool EditorExportPlatformAndroid::_should_be_transparent(const Ref<EditorExportPreset> &p_preset) const {
return (bool)get_project_setting(p_preset, "display/window/per_pixel_transparency/allowed");
}
void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset> &p_preset) {
const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml");
@ -1046,15 +1050,22 @@ void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset>
return;
}
bool should_be_transparent = _should_be_transparent(p_preset);
// Default/Reserved theme attributes.
Dictionary main_theme_attributes;
main_theme_attributes["android:windowDrawsSystemBarBackgrounds"] = "false";
main_theme_attributes["android:windowSwipeToDismiss"] = bool_to_string(p_preset->get("gesture/swipe_to_dismiss"));
main_theme_attributes["android:windowIsTranslucent"] = bool_to_string(should_be_transparent);
if (should_be_transparent) {
main_theme_attributes["android:windowBackground"] = "@android:color/transparent";
}
Dictionary splash_theme_attributes;
splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background";
splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground";
splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme";
splash_theme_attributes["android:windowIsTranslucent"] = bool_to_string(should_be_transparent);
Dictionary custom_theme_attributes = p_preset->get("gradle_build/custom_theme_attributes");
@ -2952,7 +2963,8 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
valid = false;
}
if (p_preset->get("gradle_build/use_gradle_build")) {
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
if (gradle_build_enabled) {
String build_version_path = ExportTemplateManager::get_android_build_directory(p_preset).get_base_dir().path_join(".build_version");
Ref<FileAccess> f = FileAccess::open(build_version_path, FileAccess::READ);
if (f.is_valid()) {
@ -2963,6 +2975,12 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
err += "\n";
}
}
} else {
if (_should_be_transparent(p_preset)) {
// Warning only, so don't override `valid`.
err += vformat(TTR("\"Use Gradle Build\" is required for transparent background on Android"));
err += "\n";
}
}
String target_sdk_str = p_preset->get("gradle_build/target_sdk");

View File

@ -163,6 +163,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
bool _should_be_transparent(const Ref<EditorExportPreset> &p_preset) const;
void _fix_themes_xml(const Ref<EditorExportPreset> &p_preset);
void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet);

View File

@ -5,6 +5,7 @@
<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowSwipeToDismiss">false</item>
<item name="android:windowIsTranslucent">false</item>
</style>
<!-- GodotAppSplashTheme is auto-generated during export. Manual changes will be overwritten.
@ -13,5 +14,6 @@
<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
<item name="android:windowIsTranslucent">false</item>
</style>
</resources>

View File

@ -70,6 +70,7 @@
android:launchMode="singleTask"
android:process=":GodotGame"
android:autoRemoveFromRecents="true"
android:theme="@style/GodotGameTheme"
android:supportsPictureInPicture="true"
android:screenOrientation="userLandscape">
<layout

View File

@ -2,10 +2,14 @@
<resources>
<style name="GodotEditorTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name ="android:navigationBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="GodotGameTheme" parent="GodotEditorTheme">
<item name="android:windowIsTranslucent">true</item>
</style>
<style name="GodotEditorSplashScreenTheme" parent="Theme.SplashScreen.IconBackground">
<!-- Set the theme of the Activity that directly follows your splash
screen. This is required. -->

View File

@ -478,19 +478,24 @@ class Godot(private val context: Context) {
editText.setBackgroundColor(Color.TRANSPARENT)
// ...add to FrameLayout
containerLayout?.addView(editText)
// Check whether the render view should be made transparent
val shouldBeTransparent =
!isProjectManagerHint() && !isEditorHint() && java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/per_pixel_transparency/allowed"))
Log.d(TAG, "Render view should be transparent: $shouldBeTransparent")
renderView = if (usesVulkan()) {
if (meetsVulkanRequirements(activity.packageManager)) {
GodotVulkanRenderView(host, this, godotInputHandler)
GodotVulkanRenderView(host, this, godotInputHandler, shouldBeTransparent)
} else if (canFallbackToOpenGL()) {
// Fallback to OpenGl.
GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl)
GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
} else {
throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
}
} else {
// Fallback to OpenGl.
GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl)
GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
}
if (host == primaryHost) {

View File

@ -46,7 +46,6 @@ import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;
import android.os.Build;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.KeyEvent;
@ -83,7 +82,7 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
private final GodotRenderer godotRenderer;
private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
public GodotGLRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl) {
public GodotGLRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl, boolean shouldBeTranslucent) {
super(host.getActivity());
this.host = host;
@ -91,7 +90,7 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
this.inputHandler = inputHandler;
this.godotRenderer = new GodotRenderer();
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
init(xrMode, false, useDebugOpengl);
init(xrMode, shouldBeTranslucent, useDebugOpengl);
}
@Override

View File

@ -38,7 +38,7 @@ import android.annotation.SuppressLint;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.graphics.PixelFormat;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.KeyEvent;
@ -57,7 +57,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
private final VkRenderer mRenderer;
private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
public GodotVulkanRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler) {
public GodotVulkanRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, boolean shouldBeTranslucent) {
super(host.getActivity());
this.host = host;
@ -67,6 +67,10 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
setFocusableInTouchMode(true);
setClickable(false);
if (shouldBeTranslucent) {
this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
}
}
@Override