UPDATE 2/21/2008: See this post for the latest version of Reflector.as. I’ll reintegrate it into the source code linked to from this post eventually, but for now you’ll need to download the code from this post, then replace Reflector.as with the version from the newer post.

Okay, enough highfalutin’ chatter. This entry actually has (arguably) useful code in it.

The reflection effect is destined to become one of those gratuitous UI clichés, like brushed metal and gleaming highlights. But hey, the kids seem to like it, so I figured I’d try to make a similar component in Flex.

Before jumping into the explanation, here’s the demo:

You can drag the panel around to watch the reflection follow it, and play with the sliders to tweak the look of the reflection.

I’m not the first to attempt this: Trey Long created a reflection effect in an early beta of Flex 2 (the demo pointed to from that link no longer works with the shipping version of Flash Player 9). However, his version created static reflections—so, for example, you could see the reflection of a Button, but if you moused over or clicked on it, the reflection wouldn’t update to reflect (no pun intended) the visual changes to the Button.

I decided to make the reflection “live”—as you interact with the components being reflected, the reflection actually updates in (near) real-time. I also componentized the reflector itself, so you can just target it at a component and it automatically positions itself appropriately. I used code very similar to Trey’s reflection filter to render the reflection bitmap, though the way I actually draw it to the screen is a little different.

Read on for an explanation of the code:

The reflection component is called Reflector, in the com.rictus.reflector package. If you take a look at the demo application, LiveReflection.mxml,
you’ll see that the invocation of the Reflector component is quite simple:

<reflector:Reflector target="{panel}" alpha="{alphaSlider.value}" falloff="{falloffSlider.value}"/>

Reflector is a UIComponent, so you can stick it anywhere a UIComponent goes. It defines two properties:

  • target: the component you want to reflect. The reflector automatically tries to position itself below this component, so the target component should live inside an absolutely-positioned container (like a Canvas or a Panel with layout="absolute"), and the reflector should be inside the same container.
  • falloff: a value between 0 and 1 that represents how much of the target component to reflect. 0 means to reflect none of the component, 1 means to reflect all of it, and values in between essentially set the ratio of the height of the component that will be reflected. In the demo, this defaults to 0.6.

In addition, you’ll also want to set the alpha value to a low number (it defaults to 0.3 in the demo). Play around with the sliders in the demo to get a feel for how these values affect the look of the reflection.

To see how it works, look at the source code for Reflector. Reflector listens for updateComplete events on its target, which fire every time the target’s updateDisplayList() method is called. The Reflector then updates its own display list to draw the reflection.

Here are some notes on the various methods involved.

set target(): This is where we register our listeners for events on the target. Note that when we register for the updateComplete event, we pass true for the useCapture argument to addEventListener(). This is necessary because updateComplete isn’t a “bubbling” event—it doesn’t automatically pass up to listeners that are set on ancestors of the updated item. For example, in the demo, if we didn’t get update events from descendants, you wouldn’t see the reflection update when you twiddle one of the sliders; you would only see it update if the outer Panel itself changed.

We can get around this by attaching a “capture” listener for the event; because of the way the Flash event mechanism works, this lets us see update events for all descendants of the target component, not just the component itself. I’ll have more to say about capture in a future entry.

handleTargetUpdate(), handleTargetMove(), handleTargetResize(): When the target is updated, we call invalidateDisplayList(), which tells Flex to redraw us (by calling updateDisplayList()) at the next available opportunity. If the target is moved or resized, we update our own position and size to match.

createBitmaps(): In order to avoid allocating and reallocating bitmaps every time we redraw, we allocate the bitmap storage once in createBitmaps(). We throw away these cached bitmaps whenever the target or the size of the target changes, but otherwise we can just keep redrawing into them. We also create the alpha gradient in createBitmaps(), since that gradient doesn’t need to change unless the target or its size changes:

updateDisplayList(): This is where we do the actual drawing of the reflection. The algorithm is pretty close to Trey’s original code:

  1. Draw the target component into _targetBitmap.
  2. Combine the target bitmap with the pre-calculated alpha gradient and blit the result into _resultBitmap.
  3. Create a matrix transform to flip the image upside down.
  4. Use beginBitmapFill() and drawRect() to blit the bitmap into the display list.

And that’s it! Now, some caveats.

  • You may notice that I set showDataTips="false" on the sliders in the demo. I turned them off because the data tips don’t actually show up in the reflection (like vampires, I guess), since they’re not children of the Panel.
  • I also set liveDragging="true" on the sliders. Without this, they don’t seem to send out updateComplete events as they’re being dragged. (One glitch in the demo is that if you click on the slider track rather than dragging it, the reflection doesn’t update as the slider animates to its new position, presumably because update events don’t get sent out during the animation even if liveDragging is set.)
  • This isn’t visible on the black background, but normally Panels have a small dropshadow around them. That dropshadow doesn’t get drawn into the reflection; I’m guessing that BitmapData.draw() doesn’t render bitmap filters that are attached to the target component.

In a future entry, I’ll describe the draggable panel component that’s included in this demo.

Please let me know if you find a bug, or have any questions about how it works.

(Addendum: Someone noted that this code is GPL’ed. I did this because Trey’s original code was GPL’ed, and I didn’t go through the motions of reinventing the wheel. Future code I publish will likely be under some flavor of Creative Commons license, for easier reuse.)

Digg!digg this | del.icio.usdel.icio.us | 48 Comments