Why does my Android transition ignore the TransitionListener?
Asked Answered
J

2

3

I'm trying to make a smoothly-updating display by calling TransitionManager.beginDelayedTransition(viewGroup, new AutoTransition()) before updating the display. But I found that sometimes I update the display rapidly and Android gets confused.

So I added a TransitionListener to the AutoTransition where transitionEnd() calls back, saying the transition is complete, at which point I animate to the new state of the screen. But I find that usually this is not even called.

Is there some way of having a callback called back either when a) a transition ends or b) a transition never gets called in the first place?


More information:

I'm using Xamarin.Android. I have a section of code that looks like this which initiates the transition:

if (animated && viewGroup != null && Utils.Api19PlusKitkat) {
    App.Log("** Beginning animation.");
    var transition = new AutoTransition();
    transition.AddListener(new TransitionListener(this));
    TransitionManager.BeginDelayedTransition(viewGroup, transition);
}

TransitionListener is pretty simple:

public class TransitionListener : Java.Lang.Object, Transition.ITransitionListener {
    readonly TreeNode owner;

    public TransitionListener(TreeNode owner)
    {
        this.owner = owner;
    }

    public void OnTransitionEnd(Transition transition)
    {
        App.Log("**** TransitionListener: Transition End.");
        owner.FinishTransition();
    }

    public void OnTransitionCancel(Transition transition) {}
    public void OnTransitionPause(Transition transition) {}
    public void OnTransitionResume(Transition transition) {}
    public void OnTransitionStart(Transition transition) {
        App.Log("**** TransitionListener: Transition Start.");
    }
}

When I look at my application log, I see a bunch of cases of "Beginning animation" but very rare cases of "Transition Start" or "Transition End" (currently I'm not seeing any instances but sometimes I've seen it happen in the log).

Jolly answered 28/12, 2015 at 17:57 Comment(0)
P
3

Upd2:

I think, I got it now. After 2 days of fruitless attempts, I managed to repro the issue.

TransitionManager.beginDelayedTransition() will fire up something only when there's something to update on the screen.

For example, if you setOnClickListener with TransitionManager.beginDelayedTransition() in OnClick to the button with custom background without selector (android:background="#000", instead of the default selector) - nothing will happen, Transition won't start at all (as you see in your case). At the same time, if you assign same OnClickListenerto the default-styled button (i.e. with selector as a background) - Transition will start immediately.

Same, if you do some UI-changes after TransitionManager.beginDelayedTransition() being called:

public void changeScene(View v){
    AutoTransition autoTransition = new AutoTransition();
    autoTransition.setDuration(3000);
    autoTransition.addListener(new Transition.TransitionListener() {
        @Override
        public void onTransitionStart(Transition transition) {
            Toast.makeText(MainActivity.this, "start", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onTransitionEnd(Transition transition) {
            Toast.makeText(MainActivity.this, "end", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onTransitionCancel(Transition transition) {}

        @Override
        public void onTransitionPause(Transition transition) {}

        @Override
        public void onTransitionResume(Transition transition) {}
    });

    TransitionManager.beginDelayedTransition(container, autoTransition);
    findViewById(R.id.btn1).setVisibility(
            (findViewById(R.id.btn1).getVisibility()) == View.VISIBLE?
                     View.INVISIBLE : View.VISIBLE);
}

This will code snippet will work, as it should:

enter image description here

But once you remove the setter for Visibility, Transition is adding to the backlog of the TransitionManager and not executed (the name of the method says - it might be delayed). And it will be executed only during next UI-change:

enter image description here

In this example, I removed the the setVisibility() - and you can see the result: "animation" (in my case just Toasts) started only after I clicked on the button with Selector as a background (i.e. UI-change happens).

So the fix is going to be - "make sure there's some UI change just after you call TransitionManager.beginDelayedTransition()".

I hope, it helps

P.S. It's kind of strange though, that TransitionManager.go(), written after TransitionManager.beginDelayedTransition(), won't work. The only workaround I found here is to put it inside onTransitionEnd for the delayed transition.

Upd1:

First thought was that it might be related to Xamarin. Then I found two examples which are using BeginDelayedTransition in Xamarin.Android: xamarin/monodroid-samples and garuma/Moyeu. To check if your issue relates to Xamarin.Android or not - I suggest to debug these 2 projects to see if TransitionListener fires Transition-events.

Both examples uses TransitionManager.beginDelayedTransition() with one parameter (viewGroup). Checking source code of TransitionManager shows, that 1-parameter method is is calling beginDelayedTransition(sceneRoot, null);

public static void beginDelayedTransition(final ViewGroup sceneRoot) {
    beginDelayedTransition(sceneRoot, null);
}

and null replaces by sDefaultTransition:

private static Transition sDefaultTransition = new AutoTransition();
....
public static void More ...beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
    if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
        if (Transition.DBG) {
            Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
                    sceneRoot + ", " + transition);
        }
        sPendingTransitions.add(sceneRoot);
        if (transition == null) {
            transition = sDefaultTransition;
        }
        final Transition transitionClone = transition.clone();
        sceneChangeSetup(sceneRoot, transitionClone);
        Scene.setCurrentScene(sceneRoot, null);
        sceneChangeRunTransition(sceneRoot, transitionClone);
    }
}

So to debug it, you'll have to get the default TransactionManager.getDefaultTransition() and add your TransitionListener into it.

If it works - then we'll have to find the issue in your code. If it doesn't work - well.. Then, presumably, we found a system bug, which we can file.

Pinstripe answered 1/1, 2016 at 10:7 Comment(7)
I've added some further details in my post. Not sure it's terribly helpful though. :)Jolly
@AnthonyMills I'm continue act as Captain Obvious, but I found two examples which are using BeginDelayedTransition in Xamarin.Android: xamarin/monodroid-samples and garuma/Moyeu So, I assume, that it should work in Xamarin projects. Can you debug these examples on your device and see if their animation-listeners are fired?Pinstripe
(you'll need to do TransactionManager.getDefaultTransition() and add your TransitionListener for it, as both of the examples use TransitionManager.BeginDelayedTransition() with the default AutoTransition as a second parameter - see source code of TransitionManager: grepcode.com/file/repo1.maven.org/maven2/org.robolectric/…) By doing it, we'll figured out if it's a framework bug or something wrong with your app, so we'll eliminate half of hypothesis :-)Pinstripe
@AnthonyMills I believe, I finally managed to repro the issue and find the workaround for it. Can you check, if it helps?Pinstripe
This is an impressive amount of work and definitely deserving of the bounty. Thanks very much! So the only way is to make sure something's changing. I'll have to think what works best with my system.Jolly
@AnthonyMills my pleasure! I hope, you'll figured it out. It was actually very interesting topic to investigate. If you have follow up questions - don't hesitate to write me (here or [email protected])Pinstripe
Thanks! You earned it. :)Jolly
J
0

I finally found the answer to my question.

I was calling TransitionManager.beginDelayedTransition on a FrameLayout which did not have children at the time I called, then I added a child.

This doesn't cause a transition to happen, and no callbacks are called.

I now check if the ViewGroup I'm calling beginDelayedTransition on has children before doing the call; if it doesn't, I don't bother trying to animate it.

Jolly answered 29/1, 2016 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.