Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Added support for transparent FlutterActivitys (#32740). #9115

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
Expand Down Expand Up @@ -75,10 +76,12 @@ public class FlutterActivity extends FragmentActivity implements OnFirstFrameRen
// Intent extra arguments.
protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
protected static final String EXTRA_INITIAL_ROUTE = "initial_route";
protected static final String EXTRA_BACKGROUND_MODE = "background_mode";

// Default configuration.
protected static final String DEFAULT_DART_ENTRYPOINT = "main";
protected static final String DEFAULT_INITIAL_ROUTE = "/";
protected static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();

// FlutterFragment management.
private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
Expand Down Expand Up @@ -114,6 +117,7 @@ public static class IntentBuilder {
private final Class<? extends FlutterActivity> activityClass;
private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT;
private String initialRoute = DEFAULT_INITIAL_ROUTE;
private String backgroundMode = DEFAULT_BACKGROUND_MODE;

protected IntentBuilder(@NonNull Class<? extends FlutterActivity> activityClass) {
this.activityClass = activityClass;
Expand All @@ -138,6 +142,28 @@ public IntentBuilder initialRoute(@NonNull String initialRoute) {
return this;
}

/**
* The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or
* {@link BackgroundMode#transparent}.
* <p>
* The default background mode is {@link BackgroundMode#opaque}.
* <p>
* Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner
* {@link FlutterView} of this {@code FlutterActivity} to be configured with a
* {@link FlutterTextureView} to support transparency. This choice has a non-trivial performance
* impact. A transparent background should only be used if it is necessary for the app design
* being implemented.
* <p>
* A {@code FlutterActivity} that is configured with a background mode of
* {@link BackgroundMode#transparent} must have a theme applied to it that includes the
* following property: {@code <item name="android:windowIsTranslucent">true</item>}.
*/
@NonNull
public IntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) {
this.backgroundMode = backgroundMode.name();
return this;
}

/**
* Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with
* the desired configuration.
Expand All @@ -146,20 +172,40 @@ public IntentBuilder initialRoute(@NonNull String initialRoute) {
public Intent build(@NonNull Context context) {
return new Intent(context, activityClass)
.putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint)
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute);
.putExtra(EXTRA_INITIAL_ROUTE, initialRoute)
.putExtra(EXTRA_BACKGROUND_MODE, backgroundMode);
}
}

@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
configureWindowForTransparency();
setContentView(createFragmentContainer());
showCoverView();
configureStatusBarForFullscreenFlutterExperience();
ensureFlutterFragmentCreated();
}

/**
* Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
* bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}.
* <p>
* For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity}
* must include {@code <item name="android:windowIsTranslucent">true</item>}.
*/
private void configureWindowForTransparency() {
BackgroundMode backgroundMode = getBackgroundMode();
if (backgroundMode == BackgroundMode.transparent) {
getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
);
}
}

/**
* Cover all visible {@code Activity} area with a {@code View} that paints everything the same
* color as the {@code Window}.
Expand All @@ -170,6 +216,11 @@ public void onCreate(Bundle savedInstanceState) {
* itself transparent.
*/
private void showCoverView() {
if (getBackgroundMode() == BackgroundMode.transparent) {
// Don't display an opaque cover view if the Activity is intended to be transparent.
return;
}

// Create the coverView.
if (coverView == null) {
coverView = new View(this);
Expand Down Expand Up @@ -210,7 +261,9 @@ private Drawable createCoverViewBackground() {
* for details.
*/
private void hideCoverView() {
coverView.setVisibility(View.GONE);
if (coverView != null) {
coverView.setVisibility(View.GONE);
}
}

private void configureStatusBarForFullscreenFlutterExperience() {
Expand Down Expand Up @@ -267,13 +320,19 @@ private void ensureFlutterFragmentCreated() {
*/
@NonNull
protected FlutterFragment createFlutterFragment() {
BackgroundMode backgroundMode = getBackgroundMode();

return new FlutterFragment.Builder()
.dartEntrypoint(getDartEntrypoint())
.initialRoute(getInitialRoute())
.appBundlePath(getAppBundlePath())
.flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
.renderMode(FlutterView.RenderMode.surface)
.transparencyMode(FlutterView.TransparencyMode.opaque)
.renderMode(backgroundMode == BackgroundMode.opaque
? FlutterView.RenderMode.surface
: FlutterView.RenderMode.texture)
.transparencyMode(backgroundMode == BackgroundMode.opaque
? FlutterView.TransparencyMode.opaque
: FlutterView.TransparencyMode.transparent)
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
.build();
}
Expand Down Expand Up @@ -432,6 +491,19 @@ protected String getInitialRoute() {
}
}

/**
* The desired window background mode of this {@code Activity}, which defaults to
* {@link BackgroundMode#opaque}.
*/
@NonNull
protected BackgroundMode getBackgroundMode() {
if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) {
return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE));
} else {
return BackgroundMode.opaque;
}
}

/**
* Returns true if Flutter is running in "debug mode", and false otherwise.
* <p>
Expand All @@ -445,4 +517,14 @@ private boolean isDebuggable() {
public void onFirstFrameRendered() {
hideCoverView();
}

/**
* The mode of the background of a {@code FlutterActivity}, either opaque or transparent.
*/
public enum BackgroundMode {
/** Indicates a FlutterActivity with an opaque background. This is the default. */
opaque,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It's worth also talking about how these are going to be used here.

https://google.github.io/styleguide/javaguide.html#s7.3-javadoc-where-required

/** Indicates a FlutterActivity with a transparent background. */
transparent
}
}