Flex Under the Hood: A Tour through the Flex Architecture
Day 3. Session 3. Ely Greenfield builds a carousel for showing images (kinda like iTunes new view)
Act 1: Build a component that will visually rotate the image on the Y axis. As there are a lot of base classes, how to decide which to use?
These 3 are all very lightweight, but can’t be used directly in MXML containers – must be enclosed in your own UIComponent. (The UIComponent supports measurement, layout, drawing, styling, focus, tooltips, etc.)
- Shapes – vector graphics
- Sprites – grouping and interaction
- Bitmaps – pixel-twiddling and bitmap manipulation
TiltingPane – our base class for the image rotation. Need some bindable properties for rotation, angle, width, height.
Flash is a “retained mode system” – once you put something on stage, it is there until you get rid of it. (versus Windows where it offloads windows, graphics, etc.) So, don’t forget to clear() when you redraw your component’s visuals. Nice and simple, but complex graphical representations can be difficult to track, and can present a performance problem.
Enter the LayoutManager. This provides a framework for managing your component’s layout, visuals, and other content:
- updateDisplayList() – draw your visuals for your component. This is where we’ll put our drawing commands for the image rotation component.
- invalidateDisplayList() – tells the layout manager to re-run updateDisplayList() at end of frame.
We use a MatrixTransform to shear the image to look similar to a rotated image. Then we set up a mask in the shape of a parallelogram, and another same shaped parallelogram for a nice white border. The mask cuts off the top of the image to complete the rotated image look. Last, we set up a reflection using the bitmap API and a nice alpha-gradient mask.
From a code perspective, you set up the Shape objects on init, and every time updateDisplayList() is called, we update the visual rotation, mask, border, and reflection based on the internal properties.
Measuring Layout: Flash and Flex have very different concepts of “size”, since flash allows you to draw outside the objects region (negative pixels, to pixels beyond the width and height). There are different kinds of sizes:
- Measured Size – would like to me - measuredWidth
- Explicit Size – assigned by the user/MXML - explicitWidth
- Percentage Size – assigned by the user/MXML but a percentage - percentWidth
- Actual Size – the actual measured size of the component – set using setActualSize()
- The “width” property – sets explicitWidth, gets the actualWidth with the exception when using percentages
- Unscaled Size – when inside your custom component, use unscaledWidth (width / scaleX, or width without the scale – obviously unscaledHeight is the counterpart)
Lastly we need to measure this component properly, so other components and containers need the updated information to arrange the component. Simply set the component height to the content’s height, and the width to a fraction of the content’s width, based on the current rotation.
Why are there multiple sizes? If not, we’d get in a vicious cycle – parent requests size, parent sets size, component measures, and back to parent requesting size. Components need to track what size they want to be independent of what a controller/container is telling it to be. This is especially obvious when considering scaling a component.
Act 2: Build a carousel that displays multiple image rotations and controls setting one “selected” item.
First we define a data provider for the images, labels, etc. What data provider to use? doesn’t really matter, all of them implement the ICollectionView, so not an issue. We just need to define an itemRenderer, in this case the built-in Image class. mx.core.IFactory is the key. (skipping the rest due to time constraints) Basically it’s just a list of the image rotation component from act 1.
Adding animation – when the selection changes, we want the position and angle to animate based on how close they are to the selected item. When moving from the 1st to the 5th selected image, we move all the items to the left, collapse the 1st image, and rotate the 5th one to be a full square (not rotated). This causes some trouble with the z-index (depth). We need to think of it in terms of where we are currently (currentIndex) and where we want to be (selectedIndex). Then we change the updateDispayList() function to work from currentIndex instead of selectedIndex. Now the component state and visual appearance are always in sync, and finally we’ve created a new property that will help with the depth issue. With a quick code update, it shows the carousel animating over each image scaling to square and back to trapezoid on the other side.
Code example on Ely’s blog at http://www.quietlyscheming.com/blog/
Tags:
Post a Comment