< Day Day Up > |
Now that we have a working, albeit simple, version of the ImageViewer class, we can add more features to it. As we do so, we'll take care not the change the public API of the class. For example, at this stage we shouldn't change the loadImage( ) method's name to loadAndDisplayImage( ) . Nor should we change the order or datatype of the parameters for the constructor or the loadImage( ) method. However, adding to the ImageViewer class's API is acceptable and normal. Changing its private methods or properties is also acceptable because such changes do not affect external code. However, changing the class's public API is dangerous and considered bad form because it forces corresponding rewrites in all code that uses the class.
We've completed the first two features (loading and displaying an image) from our list of possible functional requirements shown earlier under Section 5.2 Let's move on to the third and fourth requirements: cropping the image and giving it a border. Flash doesn't have any built-in means of altering a bitmap once it's loaded. Hence, we can't literally crop our loaded image. Instead, we must apply a mask to it, which hides unwanted areas of the image from view. To mask our image, we'll first create an empty movie clip, then draw a filled square in it, then apply that square as a mask over the image-holding clip. We'll add parameters to the ImageViewer constructor to specify the size of the mask and the position of the cropped image. At this stage of development, we've made the design decision to add two parameters to the constructor function to control cropping. Doesn't this undermine our earlier goal of a stable public API? Not necessarily . First of all, we haven't released any code publicly , so no one else is using our class yet. Second, to ensure backward compatibility, we can allow the constructor to assume sensible default values even if the new arguments are omitted when calling the constructor. To create our border, we'll create a movie clip, then draw a square outline in it. We'll place the border clip visually on top of the image. We'll retrieve the thickness and color of the border from parameters that we'll add to the ImageViewer constructor. Again, we've made the design decision to specify the border thickness and color at the time the object is instantiated . Another option would be to create a new method through which these could be set. The latter option gives the caller the flexibility to change those settings even after the object has been created. To accommodate our border and mask clips, we'll redesign the structure of our on-screen assets. Previously, we created a single movie clip, container_mc , into which we loaded an image.
Here's the original clip-creation code: container_mc = target.createEmptyMovieClip("container_mc" + depth, depth); This time we'll use container_mc merely as a holder for three movie clips: the mask clip ( mask_mc ), the border clip ( border_mc ), and a new image-holding clip ( image_mc ). We'll put the image_mc clip on the bottom (at depth 0 in container_mc ), the mask_mc clip in the middle (at depth 1), and the border_mc clip on top (at depth 2), as shown in Figure 5-1. Figure 5-1. ImageViewer movie clip structureThe question is, within the ImageViewer class, when should we create the border, mask, image, and container clips? In the first version of the ImageViewer class, we created the container_mc clip in the constructor function. We could theoretically take the same approach now (i.e., create the border, mask, and image clips in the constructor). But instead, in this version of the ImageViewer class, we'll define internal (private) methods to handle the creation of the various clips. Splitting out the work into separate methods has the following benefits:
Thus, we've made this design decision to increase our class's flexibility and maintainability. Table 5-1 lists the new clip-creation methods, all of which are declared private . Table 5-1. The ImageViewer class's new instance methods that create movie clips
Now that our clip-creation operations are separated into methods, we need to add properties that provide those methods with the following information:
Table 5-2 lists the ImageViewer class's complete set of instance and class properties, all of which are declared private . The properties indicated as class properties define a single value that all ImageViewer instances reference. Instance properties pertain separately to each instance. Table 5-2. The ImageViewer class's instance and class properties
The final change to our ImageViewer class comes in the constructor function, which must define new parameters to support the new methods and properties of the class. We must define parameters to specify:
In addition to these new parameters, we'll keep our original target and depth parameters, so the ImageViewer constructor signature is now: ImageViewer(target:MovieClip, depth:Number, x:Number, y:Number, w:Number, h:Number, borderThickness:Number, borderColor:Number) 5.5.1 ImageViewer (Take 2), Design SummaryWith the constructor redesign finished, the changes for version 2 of our ImageViewer class are complete. Here's the final class design for this version. It repeats information from tables 5-1 and 5-2 in order to show how you might represent a class during the design phase of a project, perhaps before you've actually produced any code:
5.5.2 ImageViewer Implementation (Take 2)Example 5-5 shows the actual code for the ImageViewer class, version 2. Study the comments carefully for code explanations . If some of the specific ActionScript techniques are new to you (e.g., drawing lines or masking movie clips), consult an ActionScript language reference such as ActionScript for Flash MX: The Definitive Guide (O'Reilly). Example 5-5. The ImageViewer class, take 2// ImageViewer class, Version 2 class ImageViewer { // Movie clip references private var container_mc:MovieClip; private var target_mc:MovieClip; // Movie clip depths private var containerDepth:Number; private static var imageDepth:Number = 0; private static var maskDepth:Number = 1; private static var borderDepth:Number = 2; // Border style private var borderThickness:Number; private var borderColor:Number; // Constructor public function ImageViewer (target:MovieClip, depth:Number, x:Number, y:Number, w:Number, h:Number, borderThickness:Number, borderColor:Number) { // Assign property values. target_mc = target; containerDepth = depth; this.borderThickness = borderThickness; this.borderColor = borderColor; // Set up the visual assets for this ImageViewer. buildViewer(x, y, w, h); } // Creates the clips to hold the image, mask, and border. // This method subcontracts all its work out to individual // clip-creation methods. private function buildViewer (x:Number, y:Number, w:Number, h:Number):Void { createMainContainer(x, y); createImageClip( ); createImageClipMask(w, h); createBorder(w, h); } // Creates the container that holds all the assets private function createMainContainer (x:Number, y:Number):Void { container_mc = target_mc.createEmptyMovieClip("container_mc" + containerDepth, containerDepth); // Position the container clip. container_mc._x = x; container_mc._y = y; } // Creates the clip into which the image is actually loaded private function createImageClip ( ):Void { container_mc.createEmptyMovieClip("image_mc", imageDepth); } // Creates the mask over the image private function createImageClipMask (w:Number, h:Number):Void { // Create the mask only if a valid width and height are specified. if (!(w > 0 && h > 0)) { return; } // In the container, create a clip to act as the mask over the image. container_mc.createEmptyMovieClip("mask_mc", maskDepth); // Draw a rectangle in the mask. container_mc.mask_mc.moveTo(0, 0); container_mc.mask_mc.beginFill(0x0000FF); // Use blue for debugging container_mc.mask_mc.lineTo(w, 0); container_mc.mask_mc.lineTo(w, h); container_mc.mask_mc.lineTo(0, h); container_mc.mask_mc.lineTo(0, 0); container_mc.mask_mc.endFill( ); // Hide the mask (it will still function as a mask when invisible). // To see the mask during debugging, comment out the next line. container_mc.mask_mc._visible = false; // Notice that we don't apply the mask yet. We must do that // after the image starts loading, otherwise the loading of // the image will remove the mask. } // Creates the border around the image private function createBorder (w:Number, h:Number):Void { // Create the border only if a valid width and height are specified. if (!(w > 0 && h > 0)) { return; } // In the container, create a clip to hold the border around the image. container_mc.createEmptyMovieClip("border_mc", borderDepth); // Draw a rectangular outline in the border clip, with the // specified dimensions and color. container_mc.border_mc.lineStyle(borderThickness, borderColor); container_mc.border_mc.moveTo(0, 0); container_mc.border_mc.lineTo(w, 0); container_mc.border_mc.lineTo(w, h); container_mc.border_mc.lineTo(0, h); container_mc.border_mc.lineTo(0, 0); } // Loads the image public function loadImage (URL:String):Void { // Load the JPEG file into the image_mc clip. container_mc.image_mc.loadMovie(URL); // Here comes an ugly hack. We'll clean this up in Example 5-6 // when we add proper preloading support in Version 3. // After one frame passes, the image load will have started, // at which point we safely apply the mask to the image_mc clip. container_mc.onEnterFrame = function ( ):Void { this.image_mc.setMask(this.mask_mc); delete this.onEnterFrame; } } } 5.5.3 Using ImageViewer (Take 2)Now that our ImageViewer class can crop an image and add a border to it, let's put ourselves in the position of a developer who's using the class (not creating and maintaining it). When we used the first version of the ImageViewer class, we placed the following code on frame 15 of imageViewer.fla : var viewer:ImageViewer = new ImageViewer(this, 1); viewer.loadImage("picture.jpg"); ImageViewer version 2 added two new features comprising quite a lot of code. However, none of the public API defined by ImageViewer version 1 changed in version 2; version 2 made additions only to that API. The loadImage( ) method changed internally, but its external usage did not change at all. Likewise, the constructor function changed internally, but externally it only added parameters. It retains the target and depth parameters from version 1 and adds six new parameters: x , y , w , h , borderThickness , and borderColor . Hence, users of ImageViewer version 1 can easily update imageViewer.fla to use ImageViewer version 2's new features, as follows :
Now let's consider what would happen if we upgraded from ImageViewer version 1 to version 2 without changing any code in imageViewer.fla . (For the sake of this scenario, let's presume that version 2 contains performance enhancements that have prompted us to upgrade.) We'd replace our ImageViewer.as file, as in the previous Step 1, but we'd skip Step 2, leaving the following code on frame 15: var viewer:ImageViewer = new ImageViewer(this, 1); viewer.loadImage("picture.jpg"); How would ImageViewer version 2 respond to being constructed with only two parameters? Fortunately, very well. Version 2 specifically safeguards against missing parameters. The createImageClipMask( ) and createBorder( ) methods create the mask and border clips only if a useful width and height are supplied, as shown in this excerpt from Example 5-5: if (!(w > 0 && h > 0)) { return; } Thus, when no width and height are supplied to the constructor, ImageViewer version 2's behavior matches ImageViewer version 1's behavior exactly.
|
< Day Day Up > |