How do I prevent the status bar and navigation bar from animating during an activity scene animation transition?
Asked Answered
M

9

134

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?

Mangrum answered 28/10, 2014 at 3:6 Comment(0)
B
224

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.

Berri answered 5/11, 2014 at 2:19 Comment(31)
Interesting that you needed the onPreDraw listener -- I thought the framework did that work for you :)Proboscis
@Proboscis It looks like the framework should do that work for you according to the source code... but for some reason it was necessary in order to get the desired effect.Berri
tried this, doesn't work! I wanted the transitions akin to the transitions achieved by using android:transitionName in your layout XMLs... any idea how thats possible???Infrasonic
What would I need to do to prevent the Toolbar from animating in Approach #1?Tamatamable
@EdwardvanRaak I haven't tested this myself, so I could be wrong. But at first glance it looks like the ID is named R.id.action_bar in appcompat-v7 (see the source code here: github.com/android/platform_frameworks_support/blob/master/v7/…). I might be wrong though... try it out and let me know if it works.Berri
Any ideas why findViewById(android.R.id.navigationBarBackground) is returning null on Lollipop? I am using appcompat-v7Sequacious
@Sequacious Are you calling it after you call setContentView() on the activity? Does findViewById(android.R.id.statusBarBackground) return null as well? Does it return null for both the called and calling activities during the transition?Berri
@Sequacious Not sure then. But if approach #2 doesn't work for you because it is returning null, you could always try approach #1 as well (which is probably the easier solution anyway).Berri
Any way to do approach #1 in transition xml?Hawking
@TonyWickham I edited my post to include the XML as well. Just set it as the android:window{Exit,Enter}Transition in the activity's theme. (BTW, let me know if it works for you work... I didn't test it myself and wrote it off the top of my head. :P I'm pretty sure it will work though).Berri
@Alex It doesn't stop the action bar from moving, but it does exclude/include other id's so I'm not sure what's wrong...Hawking
@TonyWickham If you want to exclude the Action Bar from the transition and you are using AppCompat, try adding <target android:excludeId="@id/action_bar_container"/> as well (see this G+ post for some other related details).Berri
What do you think about filing a bug against Android? The framework should always take care of sharing status bar and navigation bar through activity transition. I can't imagine a case when it shouldn't.Lisabeth
What is the TRANSITION_NAME for Action bar? so that i can prevent the action bar from flickeringPitanga
I don't think it has a transition name. But if you are using AppCompat v7 you can reference the action bar's ID instead (R.id.action_bar_container). Or if you are using a Toolbar instead of the action bar, you can give it whatever ID/transition name you want.Berri
Approach #2 works, but there is still a flicker (in the activity) when the transition happens. the status bar and navigation bar do not flickr anymore.Dorella
@alex This worked almost perfectly for me until we switched to the new support library with android.support.design.widget.TabLayout and android.support.design.widget.AppBarLayout - now the flickering is backWinch
I have the same problem with Approach #2. But Approach #1 works for me!Mestas
@AlexLockwood I'm having the same issue as radzio, findViewById(android.R.id.statusBarBackground) returns null for both Activities. I've tried every combination of solutions, and nothing works exactly how I'd like it to. Adding the toolbar and navigationbar to the transition works perfectly however. It's just that my view is still underneath the statusbar. Is there any other way to get a reference to the statusbar?Harvard
I guess I know why statusBar is null in my case. I'm using the DrawerLayout which just paints that area, so there is no view to move... I'll check if I can take the DrawerLayout itself. Well that causes a funny bug where the hole layout will been pushed down with the statusbar height.Fabliau
Can't we do this using fragments in both activities like "ActA+FragA -> ActB+FragB"? If not, how do you suggest we keep this animations on single pane layout, and also have dual pane layout for tablets?Adjust
Why did google think that it was a good idea that the default behavior cause the status and navigation bar to flash? This is really strange stuff.Ibnsina
This doesnt seem to work when you have a layout like this gist.github.com/lawloretienne/ef85146dcdbd2a95b96ab796a448c782 I have removed all the attributes so that you can get the gist of what viewgroups are in the layout. This layout being the details screen or second screen you want to transition to.Uela
I have found a fix for this issue and it is in my repo github.com/lawloretienne/SharedElementTransitionUela
@toobsco42 what is different in your approach, i saw ur repo and didn't find anything special about it. could you mention the pointPicturize
android.R.id.navigationBarBackground can give you a NPE on Samsung or HTC devices as they don't have an on screen navigation bar. Do a null check before adding them as shared elementsLandau
I am trying this solution on nexus 6 with android nougat. But both the approaches are not working for me .Emetic
findViewById(android.R.id.navigationBarBackground) and findViewById(android.R.id.statusBarBackground) returns null for me .Emetic
Approach #1 didn't work on Nougat for me for some reason, however approach #2 did! Thanks so much!Golfer
@AlexLockwood With an immersive target activity (with a translucent status bar), the status bar still flashes. It works if its opaque, but if translucent, the alpha animation makes it flashes briefly. Do you have a solution? Looks like the status bar background is white whatever is set, while the nav bar works well.Mollusc
How can i prevent overlapping while performing transitions?? I have tried using: window.setAllowEnterTransitionOverlap(false); and window.setAllowReturnTransitionOverlap(false);Blunger
E
4

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://mcmap.net/q/168937/-blinking-screen-on-image-transition-between-activities

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.

Embouchure answered 18/2, 2017 at 4:39 Comment(0)
G
4

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!

Gendarmerie answered 10/1, 2018 at 17:8 Comment(4)
Hi. Isn't this the same as Approach #1 in the top answer?Mangrum
@Mangrum Not entirely. As far as I can tell, he never mentions the fact that this need only be set in the destination activity.Gendarmerie
hey @Gendarmerie could you help me this: #50189786Transcalent
You must exclude the statusbar and the navigationbar in both activity, the caller and the called.Sciolism
P
3

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.

Proboscis answered 31/10, 2014 at 1:22 Comment(6)
Did this work for you? I tried this and the status bar and navigation bar still flashes white during the transition.Mangrum
Yes, it worked for me. Are you sure there isn't another view that might be overlapping them temporarily? Have you tried setting up a simple activity transition in a 'sandboxed' environment to isolate the issue?Proboscis
@Proboscis I think you also need to postpone the enter transition in order to get it working... I also wasn't able to prevent the status/navigation bars from animating simply by sharing the views. I guess you need to wait for the window's decor view to finish its layout before you let the enter transition begin.Berri
@Proboscis What my answer still doesn't address though is how to prevent the Action Bar's background color from animating during the transition. If both Activity's share the same Action Bar background color, then the action bar's background color will appear to animate as the calling Activity's action bar gradually fades out and the called Activity's action bar gently fades in. Do you have any idea how to get around this issue?Berri
@Proboscis Actually, I think there is an even better solution. You can simply exclude the navigation/status bar backgrounds as targets in the window's default exit/enter fade transition. See my updated answer for details.Berri
does android.R.id.window_status_bar exists for anyone?Goofy
F
3

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);
Feinstein answered 12/7, 2017 at 14:42 Comment(0)
F
1

getWindow().setEnterTransition(null); on the Entering transition removed the white overlay for me.

Fornax answered 28/8, 2016 at 15:50 Comment(0)
U
0

Here is how I did it. I share both the Status Bar and the Navigation Bar in the SharedElementTransition along with an ImageView :

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  View imageView = view.findViewById(R.id.iv);
  Resources resources = view.getResources();
  imageView.setTransitionName(resources.getString(R.string.transition_image_thumbnail));

  Pair<View, String> p1 = Pair.create(imageView, resources.getString(R.string.transition_image_thumbnail));

  Window window = getActivity().getWindow();

  View navigationBar = getActivity().findViewById(android.R.id.navigationBarBackground);
  View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);

  Pair<View, String> p2 = Pair.create(statusBar, statusBar.getTransitionName());
  Pair<View, String> p3 = Pair.create(navigationBar, navigationBar.getTransitionName());

  ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
          p1, p2, p3);

  ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
} else {
  startActivity(intent);
}
Uela answered 20/7, 2016 at 0:23 Comment(0)
C
0

In case of an animation

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
            android:duration="500"
            android:fromYDelta="100%p"
            android:toYDelta="0">
    </translate> <!--Define duration here-->
</set>

I ended up making the status bar transparent in @colors

<color name="transparent">#00000000</color>

for an example theme like the following in @styles

<style name="Theme.AppCompat.Translucent" parent="Theme.MaterialComponents.Light.NoActionBar">
        <item name="android:background">@android:color/transparent</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@android:style/Animation</item>
        <item name="android:statusBarColor">@color/transparent</item>
    </style>
Callihan answered 24/12, 2020 at 6:13 Comment(0)
M
0

If someone using androidx and find
fade.excludeTarget(android.R.id.navigationBarBackground, true);
not work, it just because this window cannot find the view with this id, you should replace with this.excludeTarget(androidx.appcompat.R.id.action_bar_container,true).

But now is Navigation time, so sad to know this until now.

Milo answered 19/4, 2021 at 18:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.