How do I prevent the status bar and navigation bar from animating during an activity scene animation transition?
Firstly, my status bar background is set to dark brown, and my navigation bar background is default black. I'm using the Material light theme.
I'm starting a new activity using ActivityOptions.makeSceneTransitionAnimation
with default transitions, and I notice that both the status and navigation bars briefly fade to white and then back to the correct colors.
According to the documentation:
To get the full effect of a transition, you must enable window content transitions on both the calling and called activities. Otherwise, the calling activity will start the exit transition, but then you'll see a window transition (like scale or fade)
I am using getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
on both the calling and called activities.
Similarly, if I change the enter transition to a slide, both the status and navigation bars briefly have a slide transition with a white background.
How do I prevent the status bar and navigation bar from animating during an activity scene animation transition?
There are two approaches you can use that I know of to prevent the navigation/status bar from animating during the transition:
Approach #1: Exclude the status bar and navigation bar from the window's default exit/enter fade transition
The reason why the navigation/status bar are fading in and out during the transition is because by default all non-shared views (including the navigation/status bar backgrounds) will fade out/in in your calling/called Activitys respectively once the transition begins. You can, however, easily get around this by excluding the navigation/status bar backgrounds from the window's default exit/enter Fade
transition. Simply add the following code to your Activitys' onCreate()
methods:
Transition fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setExitTransition(fade);
getWindow().setEnterTransition(fade);
This transition could also be declared in the activity's theme using XML (i.e. in your own res/transition/window_fade.xml
file):
<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
<target android:excludeId="@android:id/statusBarBackground"/>
<target android:excludeId="@android:id/navigationBarBackground"/>
</targets>
</fade>
Approach #2: Add the status bar and navigation bar as shared elements
This approach builds off of klmprt's answer, which almost worked for me... although I still needed to make a couple of modifications.
In my calling Activity, I used the following code to start the Activity:
View statusBar = findViewById(android.R.id.statusBarBackground);
View navigationBar = findViewById(android.R.id.navigationBarBackground);
List<Pair<View, String>> pairs = new ArrayList<>();
if (statusBar != null) {
pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
}
if (navigationBar != null) {
pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
}
pairs.add(Pair.create(mSharedElement, mSharedElement.getTransitionName()));
Bundle options = ActivityOptions.makeSceneTransitionAnimation(activity,
pairs.toArray(new Pair[pairs.size()])).toBundle();
startActivity(new Intent(context, NextActivity.class), options);
So far this is essentially the same thing that klmprt suggested in his answer. However, I also needed to add the following code in my called Activity's onCreate()
method in order to prevent the status bar and navigation bar from "blinking" during the transition:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next);
// Postpone the transition until the window's decor view has
// finished its layout.
postponeEnterTransition();
final View decor = getWindow().getDecorView();
decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decor.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
}
Adding the status bar and navigation bar backgrounds as shared elements will force them to be drawn on top of the window's default exit/enter fade transition, meaning that they will not fade during the transition. More discussion about this approach can be found in this Google+ post.
Completely prevent Activity transitions from interfering with shared element transitions:
On the exiting activity, call getWindow().setExitTransition(null);
On the entering activity, call getWindow().setEnterTransition(null);
From https://stackoverflow.com/a/34907685/967131
I suspect this may have side effects, but don't know for sure. It is dead simple and works though.
Prevent specific elements from blinking:
I started with Alex Lockwood's answer and did a fair bit of experimentation to try to get it working. The core of it is correct, although I didn't need the code he suggests for the receiving Activity, but I ran into some problems by calling it in a Fragment (instead of an Activity) and by setting a toolbar as the action bar.
Oh, the Fragment thing? I saw a lot of comments that trying to retrieve references to the status bar and navigation bar were null. The same thing happened to me as well, until I realized I wouldn't find those in the Fragment's layout... they were above that level. Hence, the code below to get the decor view from the Activity and search that. Then I found them with no problem.
In the end, I developed this utility method:
public static Bundle transitionOptions(Activity activity, int transitionViewResId, int transitionNameResId) {
if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
return null;
}
View decorView = activity.getWindow().getDecorView();
View statusBar = decorView.findViewById(android.R.id.statusBarBackground);
View navigationBar = decorView.findViewById(android.R.id.navigationBarBackground);
View appBarLayout = decorView.findViewById(**R.id.appbarlayout**);
View transitionView = decorView.findViewById(transitionViewResId);
String transitionName = activity.getString(transitionNameResId);
List<Pair<View, String>> pairs = new ArrayList<>();
pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
if (appBarLayout != null) {
pairs.add(Pair.create(appBarLayout, activity.getString(**R.string.transition_appbarlayout**)));
}
pairs.add(Pair.create(transitionView, transitionName));
//noinspection unchecked - we're not worried about the "unchecked" conversion of List<Pair> to Pair[] here
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs.toArray(new Pair[pairs.size()]))
.toBundle();
}
Note R.string.transition_appbarlayout and R.id.appbarlayout. These IDs are arbitrary, as long as they match what your code uses. In my XML, I layout the custom action bar like so (edited down to the essentials):
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
android:id="**@+id/appbarlayout**"
android:transitionName="**@string/transition_appbarlayout**">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"/>
</android.support.design.widget.AppBarLayout>
If you don't use a Toolbar like this, that part can be removed from the utility method.
Then you would call it in your Fragment like so:
startActivity(intent, UIUtils.transitionOptions(getActivity(),
R.id.**my_view**,
R.string.**transition_my_view**));
Using whatever values you want, as long as it matches your XML.
This prevents the status bar, the tool bar and the navigation bar (back / home / recent apps buttons) from flashing during the transition. The rest of the Activity transition is normal.
In my case, our app theme has a android:windowBackground
of blue. This causes a blue flash in the transition, which is quite frustrating. But rather than make a change that affects the entire app like that, for now I am going with the first, quick and dirty option.
I just had this same issue, and the answers appear to be missing a critical piece to the puzzle. Remember that on a shared element transition, everything happens in the Destination Activity.
In order to remove the flashing effect, simply add the following to the activity being called:
Fade fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setEnterTransition(fade);
getWindow().setExitTransition(fade);
This should solve your problem!
You need to share them in ActivityOptions.makeSceneTransitionAnimation.
E.g:
ActivityOptions.makeSceneTransitionAnimation(... Pair.create(activity.findViewById(android.R.id.window_status_bar), Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME)
(excuse the psuedo; I don't have the exact android.R.id value on hand)
You can run an appropriate transition after having shared the views.
As far as I understand this is caused by activity transition overlap. To overcome this issue I have used the following values in the onCreate()
methods of both activities:
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);