theboywhocriedWoolf | Tal Woolf

AIR BitmapData Scrolling Component

16

Interactive lists for Android and iOS

AIR has come a long way in helping developers bridge the gap between desktop and mobile development at the touch of a button, which of course is all dependent on your IDE.

Thanks to our good friend Stage3D the latest release of AIR 3.3 offers faster GPU rendering for iOS and Android  , while improvements in its native extensions compatibility have made it easier to harness even more power running scripts natively, thus giving AIR even more access to native mobile functionality.

This is all well and good, but unless you are making an amazing 3D game, utilising Unity or taking advantage of frameworks such as Starling, to render all your 2D graphics to the GPU, applications can still feel a little jerky, non responsive and may even suck your applications memory dry leaving nothing but a worn out skeleton that’s only attribute is its ability to be compiled to multiple platforms.

So taking  AIR’s unforeseen quirks into account making interactive list components could turn you grey, before your time, whilst trying to find a solution.

My Custom BitmapData Scroller provides an easy way to create interactive scrolling views without having to create interactive display elements. Keeping both the CPU and GPU happy.

Cutting the Fat

First things first, wherever possible it is important to cut your applications fat. Reduce it to that slender application you once fell in love with before you deploy it and soon find it unrecognisable even to your touch

One dietary recommendation to help you do that, as my previous post stipulates, is… “ Bitmap that Data “

The Problem

If you have ever tried to make an image scrolling application, or simply tried to make a seamless scrolling background, without chopping down your assets, then you will probably already be familiar with AIR’s little quirks that arise when dealing with long display objects..

Before AIR 3, the largest BitmapData width was restricted to 2,880 pixels. This restriction has now been removed and the maximum width is completely dependent on the operating system.

This may give you some peace of mind but I found that if you try to simply create a list of thumbnails for example, that spans a width of tens of thousands of pixels, then you start getting degradation in image quality and run into performance issues.

Scrolling the right way

After trying multiple solutions, the one that I found worked the best was to create a BitmapData Scroller. Not in the sense of utilising the BitmapData scroll property but to create an array of BitmapData elements and scroll them on a single BitmapData canvas. This technique builds upon Jesse Freeman’s  “Advanced Blitting: Bitmp Scroller for web, Desktop and Mobile”.

At the heart of it the Scroller takes in a Vector of BitmapData elements and stores their positions and size in a Vector of Rectangles. Then as the Scroller position is updated elements in view are calculated based on their position and the canvas dimensions. If in view they are drawn into the canvas’ BitmapData, using CopyPixels, each time calculating the offset and any left over pixels.

This all happens in the draw(); method:


/*
* draw the data, keep looping until the area of the canvas has been filled
*/
override protected function draw( sampleArea : Rectangle, offset:Point = null ) : void
{
     _calculationPoint.x = sampleArea.x;
     _calculationPoint.y = sampleArea.y;

     _collectionID = calculateCollectionStartIndex(_calculationPoint);

     if ( _collectionID != -1)
     {
          _sourceRect = _collectionRects[_collectionID];

          _sourceBitmapData = _bitmapDataCollection[_collectionID];

          _leftover = Math.round(calculateLeftOverValue(sampleArea.x, sampleArea.width, _sourceRect));

     if (!offset)
          offset = _copyPixelOffset;

     point = calculateSamplePosition(sampleArea, _sourceRect);

     sampleArea.x = point.x;
     sampleArea.y = point.y;

     bitmapData.copyPixels(_sourceBitmapData, sampleArea, offset);

     if ( _leftover > 0 )
     {
           offset = new Point( bitmapData.width - _leftover, 0);
           var leftOverSampleArea:Rectangle = calculateLeftOverSampleArea(sampleArea, _leftover, _sourceRect );

           draw(leftOverSampleArea, offset);
      }

    }
}

The result leaves you with a smooth and efficient way to scroll your visual elements  within one Bitmap as the canvas, with no need to add or remove display objects from the display list and no need to shift any display objects back and forth to append them to the beginning or end as you scroll your display elements.

Some Extra Trimmings

Jesse Freeman’s scrolling implementation worked very nicely but I needed a bit more from my application. For example, I needed to simulate a type of iOS list behavior- tweening a list of thumbnails back into place if dragged outside of its bounding area. I also needed the ability to scroll multiple elements in a list and have them snap to the nearest bounding point when moving below a certain speed.

All the extra functionality started to mount up so I decided to rewrite the BitmapData Scroller adding some extra trimmings on the side.

Blueprints

I won’t be going through my code in great detail, as most of it is commented and self-explanatory, instead I’ll take you through the structure and some of the core functionality my custom BitmapData Scroller component.

Throughout the Scroller I have tried to abstract the components core functionality where possible to allow you to easily change or plug in new functionality by simply creating or extending classes.

The component is made up of six main core elements, their responsibilities are as follows:

Component interface classes – The components main interface contains all the core component properties and instantiates all other classes; mediating communication between them.

Event management – Handles all event management and user interaction, automatically switching between Mouse Events and Touch Events.

Animator – Responsible for updating the Scroller positioning, applying friction, ease and checking bounds

BitmapDataScroller -  Contains all the BitmapData positioning information and is responsible for writing and manipulating the BitmapData to the canvas when the component is interacted with.

Interaction – Calculates the components positioning and is responsible for sending updated positioning information to the Animator which in turn moves the visual elements.

TimeStepper – Contains the animation loop and uses time stepping to try and keep the animation constant independent of individual device processing power.

You will also notice that each section has separate classes to control vertical and horizontal elements of the component.. This way the classes are instantiated as and when they are needed, making it easier and neater to contain their logic to separate classes.

Up Up and Away

Getting started with the component is relatively easy. I will take you through a few steps and make you aware of various features available, the rest will be up to you to experiment and play around with.

The component accepts a vector of BitmapData. If you are unsure of how to do this take a look at the AS3 Reference guide and its all pretty self explanatory.

Once you have your collection of BitmapData it’s time to get the party started.  Instantiating the component with its default functionality is easy.


private var _scroller : IScrollerComponent;

_scroller = new ScrollerComponent();
_scroller.bitmapDataCollection = _data;
_scroller.orientation = Orientation.HORIZONTAL;
_scroller.startingPosition = Position.LEFT;
_scroller.positionSelectedToCenter();
_scroller.initialiseScroller();

addChild( _scroller.view );

This will give you a Horizontal scrolling list that snaps its items to the canvas bounds.

Instead of setting each property separately I have created a Method that accepts the main functionality settings.


_scroller.properties

                    (

                       canvasRectangle,

                       marginAmount,

                       scrollerSnappibg,

                       bindToCanvas,

                       orientation,

                       startingPosition,

                       useEnterframe

                   );

Now these are pretty self explanatory, the only one that might not be is bindToCanvas.

If true the right most list element will not be able to go any further left than the canvas edge and when false it will be able to go all the way over to the left.

This is helpful if you have a list of thumbnails that is centered and you need to be able to scroll your list from both sides to the middle, rather than be bound to the edge of the canvas.

Extra nibbles

There are a few properties that may not immediately be self explanatory which I have listed below:

makePaddingVisible – The Scroller automatically creates BitmapData padding either side of the main display items created, this enables the Scroller position its display elements past their bounds and ease back to position without complication.

You can make this padding  visible by passing through a colour to this parameter.

iosSynthesizedLag -  Synthesising an iOS style offset to a touch position when dragging the list elements passed their bounds.

oneItemAtATime -  Allows you to create a full screen sliding view. Sliding one view element at a time to the swiped direction.

addOverStateFunctionality – If you are using plain colour BitmapData elements this enables you to add a selected state to your list components and thus simulate button behavior. Simply pass in the current element’s colour and a replacement colour and then when you select an element the component will change its colour to the over colour specified.

positionSelectedToCenter – Makes list elements position themselves to the center when selected. If no point is specified the list automatically calculates a center position.

Signaling Interaction

Throughout the component I use Robert Penner’s Signals. They are dispatched at certain points of interaction and animation, these are; when a movement starts, movement completes and when an item is selected dispatching the selected elements ID. This is really useful to keep tabs on the state of the Scroller and manage its interactions with other parts of your application

And away we go….

That’s it, you’re ready to go. I hope you put the Scrolling Component to good use. Good luck and happy scrolling, feel free to ping me back with any comments or suggestions.

Discussion

  1. Luke Massoud

    this might be GREAT! and Ill have further comments after I can test it. in order to test it first though how do I get the thing to move ?

    • Luke Massoud

      also I don’t know how to change the size of it. its just this little square event after feeding it a canvas rectangle. ps. thanks allot for this

    • boyCriedWoolf

      Hello Luke, I have added an example to the GitHub Repository, if you are still having problems let me know. Hope this helps. Thanks.

      • Luke Massoud

        Tal thanks for your quick response and the example code. so I think I might know what the problem is. I cant test it on pc (I should put it on the phone?). I tried using the touch emulation in the flash 6 IDE and it moves but really abnormally. (when i click it it just jumps). also it still looks like this http://tinypic.com/r/rtfbjl/6. It only shows the first 50 or so pixels.. thanks mate

        • Luke Massoud

          omg sorry ! the bitmaps themselves are only 50 high. my mistake ! and sorry for all these comments I would edit if I could. But im still interested to know how to test this without compiling into an app. cheers

      • Luke Massoud

        Anyways Its working great on the phone with GPU. The lack of ability to scroll without your app looking garbage is a huge short coming for iPhone Air. I’ve seen a few sad looking adobe Air apps with really rough scrolling. Your right on the money with this. I really don’t know how people have managed to scroll any significant amount of data before your solution. I already tried greensocks blitmask scrolling thing but it was slower than just scrolling a list of bitmaps. I have spent the past week running tests and trying to come up with a solution myself. It was very similar to this except rather than passing a vector of bmpdata I passed a vector of sprites and drew them to bitmap data in lots on the fly, for eg 30 during scrolling when needed to keep the memory usage down. This causes little bumps here and there. but those bmpdatas really eat the memory. the other difference is rather than use copy pixels I had several bitmaps (enough to fill the scroll area with the minimum sprite item height found in the sprite vector) I set the bitmap data to the bitmaps. would this be faster ? (copyPixle vs setting the bmpdata). I was aiming for comparable performance and smoothness to the native scroll in iOS.I managed to achieve it on the 4s after days of work. (felt like an idiot using sooo much time to achieve something so basic). But it still runs like shit on the iPhone 4 ARRGGGG. I gave up and yours is the only other viable solution I found . I have no idea why there isnt more noise about this,makes me wonder hoe many people are actually using air for phone apps. Im still trying to see if yours is better than mine and hoping it is. Also did you try using starling textures? (uses stage3d). Hopefully yours will be faster (fingers crossed) if not I will try tests with starling and also do some tests with scrolling by simply moving(setting the y position) larger bitmapdata chunks. Thanks Tal

        • boyCriedWoolf

          With regards to compilation, as long as you use an AIR compiler the Scroller should work fine for both mobile and device. With regards to performance, after some time searching and trying out other methods, this was the fastest I found, but I could be wrong. I’ll be interested to know how you get on and the best solution you finally choose to use and implement. Thanks

          • Luke Massoud

            okay, ill keep persisting. thank you very much this whole thing is laid out really well.

          • Luke Massoud

            Hello,I did some tests on an Ipod 4th gen in GPU. The test consisted of scrolling a 6000*320 BitmapData. I used Xcode instruments to get the CPU usage %.
            1. Simply put the bmp data into a bitmap and set its y position each frame:12%
            2.Used “graphics.beginBitmapFill” to fill just the scroll area
            using a matrix.12-13%
            3.used a bitmap with an intermediate bitmap data and copyPixels from the source bitmap data to simulate scrolling. 60%
            4.used the same method in 1. but using stage3d via starling: it was around 20%.

            I ended up using method 1.
            I break my scrolling up into bigger bitmap data pieces (3000 high).

          • Luke Massoud

            those tests were: moving 10 pixels a frame at 60FPS with a “quick published” ipa

  2. Peter

    Any example how to control the scrolling from outside, like a button or another scroll view. Getting the scrolled amount or just moveto a position. Works like charm!

  3. Camilo Vargas

    Tal thanks for making this component public!. Would you mind posting an example of how to listen to mouse events on the elements of the scroller component, like for an image gallery thumbnail list. Thanks in advanced.

    • boycriedwoolf

      I have updated the example in my Git repository to show how you can listen to various signals dispatched when interacting with the component. If in doubt try looking at the interface to see what methods are available, if you still need further functionality feel free to add it and commit it back into the repository, it might be useful for others. Thanks

  4. Norak

    Do you mind if I ask if this possible to use with movie clip that click able or it only show bitmap image?

    • boycriedwoolf

      I am not really sure what you are asking. This scroller only accepts BitmapData. Take a look at the example within GitHub to see a simple example. Thanks.

Leave a Reply to boyCriedWoolf / Cancel Reply

(* Required)