I ran into a minor issue yesterday with an application I’m working on in my spare time. The application itself has a state machine underneath the surface which sends messages to the UI layer at certain state changes. One of those state changes triggers a UI animation to display the results.
And the animation was broken.
I found out why. In one execution path through the application, the same animation was being triggered twice. And when this happened, the animation was getting confused.
The solution, once I realized the problem (triggering an animation twice in rapid succession screws up the animation sequence) was simple: create a new event object which gets executed after the current event has completed processing in the event loop. In my case I set a flag which indicates if I’ve inserted my event to trigger my animation into the event loop, and when the animation is triggered, I clear the flag. (On the iPhone:
- (void)internalDoMyAnimation { /* Trigger animation here */ enqueueAnimation = NO; } - (void)doMyAnimation { /* * We enqueue this for the next loop through the run loop to make sure we * only do this once. Two rapid calls to scrollToRowAtIndexPath animated * just confuses the animation code. */ if (enqueueAnimation) return; enqueueAnimation = YES; [self performSelectorOnMainThread:@selector(internalDoMyAnimation) withObject:nil waitUntilDone:NO]; }
The idea is simple: if for whatever reason -doMyAnimation gets triggered twice, the animation event will only be enqueued once. Parameters can be stored in the object itself, so if I have two ways to do the animation, I can trigger the correct behavior. And enqueueing the animation has the advantage of allowing the animation code to trust that the current application state has settled before actually doing something.
Every application framework allows an event to be inserted into the main (UI) thread: on Swing it’s SwingUtilities.invokeLater(Runnable doRun); on Windows it’s PostMessage(). Android is a little weird; you’d think Activity.runOnUIThread() would do the trick, but the class is executed immediately if triggered from the main thread by calling the Runnable’s run() method. Instead, you have to create a Handler when you create your Activity, and use Handler.post(Runnable r) to add the object to the message queue for that activity. (If only the activity would return it’s Handler…)
And in GWT (which I’m just now playing with) it’s DeferredCommand.runCommand(Command c).