Skip to content
Shai Almog edited this page Jan 14, 2016 · 14 revisions

There are many ways to animate and liven the data within a Codename One application, one which we already discussed is the layout animations mechanism however there is a great more.

Low Level Animations

To understand the flow of animations in Codename One we can start by discussing the underlying low-level animations and the motivations behind them. The Codename One event dispatch thread has a special animation “pulse” allowing an animation to update its state and draw itself. Code can make use of this pulse to implement repetitive polling tasks that have very little to do with drawing.

This is helpful since the callback will always occur on the event dispatch thread.

Every component in Codename One contains an animate() method that returns a boolean value, you can also implement the Animation interface in an arbitrary component to implement your own animation. In order to receive animation events you need to register yourself within the parent form, it is the responsibility of the parent for to call animate().

If the animate method returns true then the animation will be painted. It is important to deregister animations when they aren’t needed to conserve battery life. However, if you derive from a component, which has its own animation logic you might damage its animation behavior by deregistering it, so tread gently with the low level API’s.

myForm.registerAnimated(this);

private int spinValue;
public boolean animate() {
   if(userStatusPending) {
       spinValue++;
       super.animate();
       return true;
   }
   return super.animate();
}

Transitions

Transitions allow us to replace one component with another, most typically forms or dialogs are replaced with a transition however a transition can be applied to replace any arbitrary component.

Developers can implement their own custom transition and install it to components by deriving the transition class, although most commonly the built in CommonTransition class is used for almost everything.

You can define transitions for forms/dialogs/menus globally either via the theme constant or via the LookAndFeel class. Alternatively you can install a transition on top-level components via setter methods.

To apply a transition to a component we can just use the Container.replace method as such:

Container c = replace.getParent();
ta.setPreferredSize(replace.getPreferredSize());
c.replaceAndWait(replace, ta, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 500));
c.replaceAndWait(ta, replace, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, false, 500));

Flip And Morph Transitions

Codename One supports a FlipTransition that allows flipping the component or form in place in a pseudo 3D approach e.g.:

Flip Transition
Morph Transition

Android’s material design has a morphing effect where an element from the previous form (activity) animates to into a different component on a new activity. Codename One has a morph effect in the Container class but it doesn’t work as a transition between forms and doesn’t allow for multiple separate components to transition at once.

To support this behavior we have the MorphTransition class that provides this same effect coupled with a fade to the rest of the UI.

Since the transition is created before the form exists we can’t reference explicit components within the form when creating the morph transition (in order to indicate which component becomes which) so we need to refer to them by name. This means we need to use setName(String) on the components in the source/destination forms so the transition will be able to find them.

Form demoForm = new Form(currentDemo.getDisplayName());
demoForm.setScrollable(false);
demoForm.setLayout(new BorderLayout());
Label demoLabel = new Label(currentDemo.getDisplayName());
demoLabel.setIcon(currentDemo.getDemoIcon());
demoLabel.setName("DemoLabel");
demoForm.addComponent(BorderLayout.NORTH, demoLabel);
demoForm.addComponent(BorderLayout.CENTER, wrapInShelves(n));
....
demoForm.setBackCommand(backCommand);
demoForm.setTransitionOutAnimator(MorphTransition.create(3000).morph(currentDemo.getDisplayName(), "DemoLabel"));
f.setTransitionOutAnimator(MorphTransition.create(3000).morph(currentDemo.getDisplayName(), "DemoLabel"));
demoForm.show();

Building Your Own Transition

In addition we can implement our own transitions, e.g. the following code demonstrates an explosion transition in which an explosion animation is displayed on every component as the explode one by one while we move from one screen to the next.

Explode transition
public class ExplosionTransition extends Transition {
    private int duration;
    private Image[] explosions;
    private Motion anim;
    private Class[] classes;
    private boolean done;
    private int[] locationX;
    private int[] locationY;
    private Vector components;
    private Vector sequence;
    private boolean sequential;
    private int seqLocation;
    public ExplosionTransition(int duration, Class[] classes, boolean sequential) {
        this.duration = duration;
        this.classes = classes;
        this.sequential  = sequential;
    }

    public void initTransition() {
        try {
            explosions = new Image[] {
                Image.createImage("/explosion1.png"),
                Image.createImage("/explosion2.png"),
                Image.createImage("/explosion3.png"),
                Image.createImage("/explosion4.png"),
                Image.createImage("/explosion5.png"),
                Image.createImage("/explosion6.png"),
                Image.createImage("/explosion7.png"),
                Image.createImage("/explosion8.png")
            };
            done = false;
            Container c = (Container)getSource();
            components = new Vector();
            addComponentsOfClasses(c, components);
            if(components.size() == 0) {
                return;
            }
            locationX = new int[components.size()];
            locationY = new int[components.size()];
            int w = explosions[0].getWidth();
            int h = explosions[0].getHeight();
            for(int iter = 0 ; iter < locationX.length ; iter++) {
                Component current = (Component)components.elementAt(iter);
                locationX[iter] = current.getAbsoluteX() + current.getWidth() / 2 - w / 2;
                locationY[iter] = current.getAbsoluteY() + current.getHeight() / 2 - h / 2;
            }
            if(sequential) {
                anim = Motion.createSplineMotion(0, explosions.length - 1, duration / locationX.length);
                sequence = new Vector();
            } else {
                anim = Motion.createSplineMotion(0, explosions.length - 1, duration);
            }
            anim.start();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void addComponentsOfClasses(Container c, Vector result) {
        for(int iter = 0 ; iter < c.getComponentCount() ; iter++) {
            Component current = c.getComponentAt(iter);
            if(current instanceof Container) {
                addComponentsOfClasses((Container)current, result);
            }
            for(int ci = 0 ; ci < classes.length ; ci++) {
                if(current.getClass() == classes[ci]) {
                    result.addElement(current);
                    break;
                }
            }
        }
    }

    public void cleanup() {
        super.cleanup();
        explosions = null;
        if(sequential) {
            components = sequence;
        }
        if(components != null) {
            for(int iter = 0 ; iter < components.size() ; iter++) {
                ((Component)components.elementAt(iter)).setVisible(true);
            }
            components.removeAllElements();
        }
    }

    public Transition copy() {
        return new ExplosionTransition(duration, classes, sequential);
    }

    public boolean animate() {
        if(sequential) {
            if(anim != null && anim.isFinished() && components.size() > 0) {
                Component c = (Component)components.elementAt(0);
                components.removeElementAt(0);
                sequence.addElement(c);
                c.setVisible(false);
                if(components.size() > 0) {
                    seqLocation++;
                    anim.start();
                }

                return true;
            }
            return components.size() > 0;
        }
        if(anim != null && anim.isFinished() && !done) {
            // allows us to animate the last frame, we should animate once more when
            // finished == true
            done = true;
            return true;
        }
        return !done;
    }

    public void paint(Graphics g) {
        getSource().paintComponent(g);
        int offset = anim.getValue();
        if(sequential) {
            g.drawImage(explosions[offset], locationX[seqLocation], locationY[seqLocation]);
            return;
        }
        for(int iter = 0 ; iter < locationX.length ; iter++) {
            g.drawImage(explosions[offset], locationX[iter], locationY[iter]);
        }
        if(offset > 4) {
            for(int iter = 0 ; iter < components.size() ; iter++) {
                ((Component)components.elementAt(iter)).setVisible(false);
            }
        }
    }
}

SwipeBackSupport

iOS7 allows swiping back one form to the previous form, Codenmae One has an API to enable back swipe transition:

SwipeBackSupport.bindBack(Form currentForm, LazyValue<Form> destination);

That one command will enable swiping back from currentForm. LazyValue allows us to pass a value lazily:

public interface LazyValue<T> {
    public T get(Object... args);
}

This effectively allows us to pass a form and only create it as necessary (e.g. for a GUI builder app we don’t have the actual previous form instance), notice that the arguments aren’t used for this case but will be used in other cases.

Clone this wiki locally