Click to See Complete Forum and Search --> : Flash and Scroll Pain
Stephen Philbin
03-03-2009, 10:55 AM
I've made a start on picking up flash and, as an exercise, I set myself the task of building a simple image gallery.
Things had been going fairly well so far and I'd learned quite a bit, but the ScrollPane has me completely stuck. I'm having two problems with my scroll pane.
1) It doesn't seem to realize its content is larger than its self and so no scroll bar is displayed and the contents can not be scrolled (not even with the mouse wheel).
2) Events (such as mouseover and click) are not fired for the Thumbnails contained in the ScrollPane.
I've tried adding the Thumbnail objects to the ScrollPane directly, and I've also tried adding the Thumbnail objects to a Sprite and then adding the container Sprite to the ScrollPanel. Neither of which have worked.
I also can't seem to get events to fire on the Thumbnail objects either. The version of the gallery I have at the moment (http://www.stephenphilbin.com/flash/projects/gallery/gallery.html) has each Thumbnail load and display at 50% opacity. When the mouse pointer is moved over a Thumbnail, it is supposed to the display at 100% opacity, but it doesn't.
Anyone got any idea what I'm doing wrong here? I've run out of ideas.
Here are the links to the gallery I'm having a problem with and the files used to make it.
The gallery (http://www.stephenphilbin.com/flash/projects/gallery/gallery.html).
The original Flash (.fla) file (http://www.stephenphilbin.com/flash/projects/gallery/gallery.fla) used to create the swf file.
The main (Gallery) class of the swf file (http://www.stephenphilbin.com/flash/lib/com/stephenphilbin/simpleGallery/Gallery.as) (also pasted below).
The Thumbnail class (http://www.stephenphilbin.com/flash/lib/com/stephenphilbin/simpleGallery/Thumbnail.as) that the Gallery class calls to display thumbnail versions of the images and contains the mouse over code for the opacity effect (also pasted below).
The directory index (http://www.stephenphilbin.com/flash/projects/gallery/) containing all the images,the XML file, the swf file and the HTML file used to display the swf file (just in case anyone wanted to have a look for some reason).
Stephen Philbin
03-03-2009, 10:55 AM
Gallery.as
package com.stephenphilbin.simpleGallery {
import flash.display.*;
import flash.geom.Rectangle;
import fl.containers.ScrollPane;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.events.Event;
import flash.events.IOErrorEvent;
public class Gallery extends Sprite {
// Constants
const MAX_IMG_DIMENSIONS:Rectangle = new Rectangle(10, 50, 380, 325);
const MAX_THUMB_DIMENSIONS:Rectangle = new Rectangle(4, 4, 79, 79);
// Configuration
var indexFileLocation:String;
var indexFile:XML;
var indexLoader:URLLoader = new URLLoader();
var imageLoader:Loader = new Loader();
var urlRequest:URLRequest = new URLRequest();
var numImages:uint;
var currentlyLoadingImage:uint;
// Components
var imageCanvas:Sprite = new Sprite();
var thumbsPane:ScrollPane = new ScrollPane();
//var thumbsPanel:Sprite = new Sprite();
var thumbsPanel:MovieClip = new MovieClip();
// Content
var images:Array;
var thumbs:Array;
// Testing
import flash.events.*;
/*
Basic overview of this class:
The constructor sorts out a few bits of config and then
loads the XML file containing the addresses of the images
that are to be shown by the gallery.
When the COMPLETE event is fired by the completion of the
loading of the XML file, the COMPLETE handler calls the
method that loads the first image from the list in the XML
file.
When the COMPLETE event is fired by the completion of the
loading of the image, scaled copies of the image are made
for display in the main image area and also as a thumbnail
in the thumbs list at the side. The copies are then
positioned by the appropriate positioning method and then
if the image is not the last image listed in the XML file
a recursive call to the same image-loading method is made.
*/
public function Gallery():void {
galleryTitle.alpha = 0.5;
//galleryTitle.thickness = 100;
galleryTitle.text = (loaderInfo.parameters['gallery_title'] === undefined ? 'Gallery Turtle' : loaderInfo.parameters['gallery_title']);
if (loaderInfo.parameters['index'] == undefined) {
indexFileLocation = "gallery_index.xml";
} else {
indexFileLocation = loaderInfo.parameters['index'];
}
urlRequest.url = indexFileLocation;
indexLoader.addEventListener(Event.COMPLETE, xmlLoadCompleteHandler);
indexLoader.addEventListener(IOErrorEvent.IO_ERROR, xmlLoadErrorHandler);
try {
indexLoader.load(urlRequest);
} catch (ex:SecurityError) {
trace('A security error has ocurred.');
}
imageCanvas.x = 10;
imageCanvas.y = 50;
thumbsPane.x = 400;
thumbsPane.y = 7;
thumbsPane.setSize(190, 386);
thumbsPane.source = thumbsPanel;
addChild(imageCanvas);
addChild(thumbsPane);
}
private function xmlLoadCompleteHandler(evt:Event):void {
try {
indexFile = new XML(indexLoader.data);
} catch (ex:TypeError) {
trace(ex);
}
numImages = indexFile.children().length();
images = new Array(numImages);
thumbs = new Array(numImages);
currentlyLoadingImage = 0;
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadCompleteHandler);
imageLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, imageLoadErrorHandler);
loadImage(currentlyLoadingImage);
}
private function loadImage(imageIndex:int):void {
urlRequest.url = indexFile.children()[imageIndex].toString();
imageLoader.load(urlRequest);
}
private function xmlLoadErrorHandler(ex:IOErrorEvent):void {
trace('An error ocurred whilst trying to load the index XML file.');
}
private function imageLoadCompleteHandler(evt:Event):void {
var originalImage:Bitmap;
originalImage = evt.target.content;
images[currentlyLoadingImage] = new Bitmap(originalImage.bitmapData.clone(), 'auto', true);
scaleImage(images[currentlyLoadingImage], MAX_IMG_DIMENSIONS);
positionImage(images[currentlyLoadingImage]);
thumbs[currentlyLoadingImage] = new Thumbnail(originalImage.bitmapData.clone(), 'auto', true);
//trace(thumbs[currentlyLoadingImage] is EventDispatcher);
scaleImage(thumbs[currentlyLoadingImage], MAX_THUMB_DIMENSIONS);
positionThumb(currentlyLoadingImage);
thumbsPanel.addChild(thumbs[currentlyLoadingImage]);
thumbs[currentlyLoadingImage].fadeIn();
imageLoader.unload();
if(currentlyLoadingImage === 0) {
imageCanvas.addChild(images[0]);
}
if(++currentlyLoadingImage < numImages) {
loadImage(currentlyLoadingImage);
} else {
trace("thumbsPanel dimensions: " + thumbsPanel.width + "x" + thumbsPanel.height);
}
}
private function imageLoadErrorHandler(ex:IOErrorEvent):void {
trace('An error ocurred whilst trying to load the image file: ' + ex);
}
private function scaleImage(image:Bitmap, maxBounds:Rectangle):void {
var scaleOnXAxis:Boolean = (maxBounds.width / maxBounds.height) <= (image.width / image.height);
var scaleRatio:Number;
if(scaleOnXAxis === true) {
scaleRatio = (maxBounds.width / image.width);
} else {
scaleRatio = (maxBounds.height / image.height);
}
image.scaleX = scaleRatio;
image.scaleY = scaleRatio;
}
private function positionImage(image:Bitmap):void {
var marginX:uint = MAX_IMG_DIMENSIONS.width - image.width;
var marginY:uint = MAX_IMG_DIMENSIONS.height - image.height;
image.x = (marginX > 0 ? Math.floor(marginX / 2) : 0);
image.y = (marginY > 0 ? Math.floor(marginY / 2) : 0);
}
private function positionThumb(index:uint) {
var row:uint;
var marginX:uint = (MAX_THUMB_DIMENSIONS.width - thumbs[index].width > 0 ? Math.floor((MAX_THUMB_DIMENSIONS.width - thumbs[index].width) / 2) : 0);
var marginY:uint = (MAX_THUMB_DIMENSIONS.height - thumbs[index].height > 0 ? Math.floor((MAX_THUMB_DIMENSIONS.height - thumbs[index].height) / 2) : 0);
if((index & 1) === 0) {
thumbs[index].x = MAX_THUMB_DIMENSIONS.x + marginX;
row = (index + 2) / 2;
} else {
thumbs[index].x = MAX_THUMB_DIMENSIONS.width + (MAX_THUMB_DIMENSIONS.x * 2) + marginX;
row = (index + 1) / 2;
}
thumbs[index].y = (MAX_THUMB_DIMENSIONS.y) + ((MAX_THUMB_DIMENSIONS.height + MAX_THUMB_DIMENSIONS.y) * (row -1)) + marginY;
}
private function eventMoo(evt:Event):void {
trace('Moo');
}
}
}
Thumbnail.as
package com.stephenphilbin.simpleGallery {
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.MouseEvent;
public class Thumbnail extends Bitmap {
var fadeTimer:Timer = new Timer(100);
var originalFilters:Array;
var mouseOverFilters:Array;
public function Thumbnail(data:BitmapData, pixelSnapping:String = 'auto', smoothing:Boolean = true):void {
super(data);
this.alpha = 0;
this.addEventListener(MouseEvent.MOUSE_OVER, setFullOpacity);/*
originalFilters = this.filters;
mouseOverFilters = [new GlowFilter()];
this.filters = originalFilters.concat(mouseOverFilters);*/
}
public function fadeIn():void {
fadeTimer.addEventListener(TimerEvent.TIMER, incrementOpacity)
fadeTimer.start();
}
private function incrementOpacity(evt:TimerEvent):void {
this.alpha += 0.05;
if(this.alpha >= 0.5) {
fadeTimer.stop();
}
}
public function setFullOpacity(evt:MouseEvent):void {
trace('moused over');
this.alpha = 1.0;
}
}
}
NXInteractif
03-04-2009, 02:52 AM
I believe if you change:
public function setFullOpacity(evt:MouseEvent):void {
trace('moused over');
this.alpha = 1.0;
}
To:
public function setFullOpacity(evt:MouseEvent):void {
trace('moused over');
evt.currentTarget.alpha = 1.0;
}
It may resolve the rollover issue.
Stephen Philbin
03-04-2009, 04:24 AM
I think in this case that both ways are probably equivalent. I can't tell, though, because the ScrollPane seems to stop the mouseover event from ever firing. I know this because the trace function I put in there for debugging never executes.
I must be doing something very, very wrong here to be having so much trouble with what should be a simple thing. Even if nobody can be bothered reading through the code I posted to find which bit needs correcting, a simple example showing how a ScrollPane containing multiple objects and actually scrolls them should be done, would probably be equally as helpful.
Stephen Philbin
03-04-2009, 08:10 AM
Well I've got the scroll bar part sorted (unless some other unexpected problem rears its head), but the mouseover problem remains.
Turns out that I needed to call ScrollPane's update method to point out to the instance of the ScrollPane that its content had changed (and was now larger than the instance its self and so needed to provide a scroll bar). The pane must have been thinking its content was still 0x0, despite the fact it was still successfully rendering the content.
Now I just need to figure out why the hell this mouseover event isn't firing. :mad:
NXInteractif
03-04-2009, 12:25 PM
Hm...I just created a very similar gallery for a client of mine...
If you go here: http://www.activedogadventures.net/ you will see at the top, some images [slowly] loading on to the page. Each image is loaded and then added to an image collection movie clip and then is also scrollable by using the mouse position. Also, when you scroll over each image, the alpha value is tweened from 0.4 to 1.0...a scenario very similar to yours.
However, the difference is that my images that are loaded are not extensions of the Bitmap class.
Each image is actually derived from the MovieClip class. And each movie clip adds the bitmap data from the loader as a child of the image movieclip.
Then I added the event listener to the image movieclip containing the bitmap, not the bitmap itself. My reason being for doing that: the Bitmap class does not inherit any Mouse events!!
NXInteractif
03-04-2009, 12:28 PM
package com.stephenphilbin.simpleGallery {
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.MouseEvent;
public class Thumbnail extends flash.display.MovieClip {
var fadeTimer:Timer = new Timer(100);
var originalFilters:Array;
var mouseOverFilters:Array;
var bData:BitmapData;
public function Thumbnail(data:BitmapData, pixelSnapping:String = 'auto', smoothing:Boolean = true):void {
this.bData = data;
addChild(bData);
this.alpha = 0;
this.addEventListener(MouseEvent.MOUSE_OVER, setFullOpacity);/*
originalFilters = this.filters;
mouseOverFilters = [new GlowFilter()];
this.filters = originalFilters.concat(mouseOverFilters);*/
}
public function fadeIn():void {
fadeTimer.addEventListener(TimerEvent.TIMER, incrementOpacity)
fadeTimer.start();
}
private function incrementOpacity(evt:TimerEvent):void {
this.alpha += 0.05;
if(this.alpha >= 0.5) {
fadeTimer.stop();
}
}
public function setFullOpacity(evt:MouseEvent):void {
trace('moused over');
this.alpha = 1.0;
}
}
}
Perhaps something like that
However, also, I would initialize the Thumbnail's alpha to something higher than 0 :D
Stephen Philbin
03-04-2009, 03:44 PM
...the Bitmap class does not inherit any Mouse events!!
Well according to Adobe's official guide to programming with Actionscript 3.0 (http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b8cbfe-7ff7.html) and Adobe's official Actionscript 3.0 API reference manual (http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/) you are completely wrong.
From the Actionscript 3.0 API reference manual page on the EventDispatcher class (http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/EventDispatcher.html) (a super-class of the Bitmap class).
EVER[/size] seen"]The EventDispatcher class is the base class for all classes that dispatch events. The EventDispatcher class implements the IEventDispatcher interface and is the base class for the DisplayObject class. The EventDispatcher class allows any object on the display list to be an event target and as such, to use the methods of the IEventDispatcher interface.
Which makes it sound rather similair to how you'd expect Java to handle this stuff. However, despite all of this, you are infact almost completely correct.
I made some small tests to check the difference between using a Bitmap and a Sprite. I used a Sprite because the manual says it's better use a Sprite than a MovieClip if you don't need the TimeLine features of the MovieClip because they are essentially identical except for that the MovieClip uses far more system resources to maintain the TimeLine and associated data (although judging by my experiences with the manuals so far, this is probably completely untrue too).
In total, I made 3 test pieces. The first two tests were a ScrollPane in the centre of the stage taking up most of the stage's space. The ScrollPane contained a single Sprite that was larger than the ScrollPane, and the Sprite, in turn, contained 4 identically sized rectangles of different colours. The only difference was that in the first test the rectangles were instances of the Bitmap Class, and in the second test they were instances of the Sprite class (and so the code to generate them was, naturally, slightly different).
The third test was of a single Bitmap in the centre of the stage with no ScrollPane as a container.
The third test confirms that you are correct about the Bitmap not dispatching events. I added a mouseover listener to output a string via the trace function and it did not happen (though it compiled without so much as a warning).
The first and second tests, however, yielded rather more interesting results. Despite having essentially the same code, they actually gave completely opposite results. The second test (using Sprites) worked perfectly and I shall now base the rest of this exercise on the second test; however, the first test (using Bitmaps) caused the mouseover and mouseout to fire, but to fire backwards. The mouseout event fired on mouseover and the mouseover event fired on mouseout. So it seems that the Bitmap class responds to mouse events in some way, but not in the way the manual says it should or even in any useful, yet undocumented, way (hence my saying that you were almost completely correct.
Well in any case, I think I might be able to finish this exercise in more-or-less the way I meant to. I think I should be able to get it all done now. I'd just like to say a very big thank you for your help and pointing me in the right direction. I probably would have just given up if it weren't for your help. ;)
NXInteractif
03-04-2009, 10:20 PM
That is very confusing, for, according to the documentation, it hardly inherits any events from the EventDispatcher base:
http://help.adobe.com/en_US/AS3LCR/Flash_10.0/flash/display/Bitmap.html
Stephen Philbin
03-05-2009, 03:11 AM
Ah. So that explains it. The documentation, yet again, failing to provide important information. Such as the fact that even if an object is a subclass of EventDispatcher, it still can't dispatch mouse events unless it is also a subclass of InteractiveObject. Just leaving people to think that so long as an object implements IEventDispatcher you're covered.
Or at least that's the impression I get anyway.
Well at least it's sorted now. Thanks again for your help. It was very much appreciated. :D