Java Dock (Launch Bar)
With the timing framework and the glass panel, you can create almost any UI component. Offering cool and complex behaviors.
In this blog I present a version of a launch bar (Dock).
Here is a screen shot of it in action:
Here is the source code under GPL.
The component extends JPanel. That provides the foundation to paint the background and the icons. It has an inner class that is the responsible of the animations as the glass pane on the parent frame. Here is where all the magic happens.
In the default conditions, when the user is not interacting with the component, the glass pane is not visible.
The Dock component paints the background, which is a shape with rounded corners and using anti-aliasing creating a smooth effect on the edges. It was tricky to get the anti-aliasing working.
You probably noticed that it uses a nice gradient.
The gradient is cached in a buffered image with the width of the image and just a couple of pixels in the height. In this way we can recreate the entire gradient using less memory, and the painting is a lot faster.
An important principle that I try to follow is to keep the code as simple as possible. Not introducing optimizations before I know that it is necessary. In this particular case, I followed the same philosophy, until I saw that the animation was not performing, when I started optimizing the code as I continue describing later in this blog.
On top of the background, the component just paints the icons in the order they were added one next to each other.
There is a mouse listener, that is waiting for the user to move the mouse over the component. At this time, we make the glass pane visible. And start animating!
The glass panel is the one that paints the animations.
The trick is to record for each icon it's current state, and the state after the animation. In this way we can calculate its size and location based on how long the animation has progressed.
Each icon has an Id which maps to its location in the Dock. When the mouse moves over the Dock, we determine the icon id that the mouse is over. We are going to call it mouseOverId.
In the glass paint method we iterate over all the icons. To determine the size of each icon after the transition is completed, we use the Math.abs of the differece between the mouseOverId and the iconId. That gives us the distance between the mouse location and the icon being painted.
One tricky thing was to determine the horizontal position of each icon. The solution was to make sure that the entire dock is always centered on the screen, and that the icons are centered in the dock. With that in mind, every time a icon expands or retracts the location of the dock is recalculated, and the icons location in the dock too. It actually works really well.
Then we use the timing framework to animate the transition from the previous size to the next size. Using acceleration and deceleration allows the animation to look very smooth.
There is another event that we listen. It is the mouse click. In that case we start another animation with the timing framework. This time to simulate a bouncing effect. For that we use a deceleration of 9.8 ~ 10 (gravity!) It looks very real. I just need to make the height of the bouncing lower and lower after each cycle
Every icon is appended with the mirror image, using the swingx ReflectionRenderer.appendReflection(...)
In the case of the bouncing, we create the reflection, and then append it at a distance proportional to the bouncing effect, to make it look more real.
When I finished codding it, it was working great in the demo app (similar to the one that you are seeing in the web start). However, as soon as I tried to integrate it with a more elaborate user interface with lots of windows and components in the background the animation started to be really slow.
I discovered a couple of things that I was doing wrong. Hopefully my learning experience will help you not to make the same mistakes:
1) Everytime you create a Graphics object, you need to call the dispose method as soon as you are done with it.
2) When you don't need a BufferedImage anymore, call the flush method
Those are 2 things that were sort of surprising to me, comming from the background of server side development, I did not expect that I had to free resources explicitly. I understand that working with graphics is resource intensive and it is extremely difficult, if not impossible to actually leave the gc to do it efficiently for you.
3) Probably the most important thing, when you call repaint from the animation trigger, call it with the clip. Specially when you are working on a transparent glass pane, and there are a lot of components underneath it. The repaint with the clipping area is going to make sure that only the components that are in that area get repainted, not the entire application. Even if your paint method does not take advantage of the clip, you should still pass it by.