Scrolling techniques—pick your poison
I’m always looking for ways to improve performance in my apps. One aspect that has always been in the back of my mind, but never implemented, is an improved scrolling technique.
Note: For the examples below, I used movies from my Netflix queue as dummy content. For demonstration purposes, the scrollRect and smooth recycling methods are not masked in these examples. Also, the names “Rigid Recycling” and “Smooth Recycling” are simply names I have given the following scrolling techniques—they are most likely not the correct terms.
scrollRect
At the moment, DestroyTwitter uses scrollRect—a property that masks a DisplayObject based on a set Rectangle. Each item in the data list is created, whether it’s visible or not. The upside to this method is scrolling speed—everything is rendered only once. The downside is memory usage—since there’s no recycling, every asset and object within each item is accounted for.
1 2 3 4 5 | // bounds:Rectangle = new Rectangle (0, 0, _Area.width, _Area.height); bounds.y = (_Content.height - _Area.height) * _Scroller.value; _Content.scrollRect = bounds; |
Rigid Recycling
An alternative is rigid recycling, which consists of only the visible items while the content of each is changed as the user scrolls. This is nice on the memory front, but smooth scrolling is a quality I just can’t strip from DestroyTwitter.
1 2 3 4 5 6 7 8 9 10 11 12 13 | $itemHeight = 50; $visibleItems = Math.round (_Area.height / $itemHeight); $invisibleItems = _data.length - $visibleItems; $A = _items.length; for ($a = 0; $a < $A; $a++) { $Item = _items[$a]; $n = Math.round ($invisibleItems * _Scroller.value) + $a; $Item.text = _data[$n]; } |
Smooth Recycling
Along with rigid recycling is smooth recycling. There are only the visible items with two others for padding. As the user scrolls, the content moves much like the pure scrollRect technique. When the content’s y-coordinate is past a certain point, however, each item changes content and the list resets its position. This keeps memory low and covers the pixel-based scrolling, allowing the same effect that scrollRect has, but on a much lighter scale.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $itemHeight = 50; $visibleItems = Math.round (_Area.height / $itemHeight); $invisibleItems = _data.length - $visibleItems; $n = Math.round ($invisibleItems * _Scroller.value); bounds.y = $itemHeight * (1 + ($invisibleItems * _Scroller.value) - $n); _Content.scrollRect = bounds; $A = _items.length; for ($a = 0; $a < $A; $a++) { $Item = _items[$a]; $n = Math.round ($invisibleItems * _scroller.value) + $a - 1; $Item.text = ($n > -1 && $n < _data.length) ? _data[$n] : ""; } |
One of the downsides to these recycling methods is rendering. Since you’re changing the content of each item whenever you scroll, AIR has to re-render everything that changes in the item. In DestroyTwitter’s case, this would be the bitmap icon, tweet text, and tweet background graphic. The scrollRect method renders once, then masks the list without having to render again. Though I’m not entirely sure how significant the speed hit would be, I’m guessing it won’t be too much since I cache user icons.
Another downside is that the item heights need to remain the same. The equation that makes them work requires a single value for the item height. This is a problem, since DestroyTwitter’s tweet heights vary based on the length of the tweet. I could standardize them, but I have a feeling it would look atrocious. Imagine a one-line tweet atop a five-line tweet. Since the item heights would have to be the max, that one-line tweet would have about 48 pixels of empty space.
What I want is a technique that might not be possible using a simple equation. It would require pre-processing to record the heights, then run on an equation that needs to reference the topmost tweet along with its height and ratio to the rest, and somehow scroll it smoothly with no hiccups. I think I consulted just about every developer I know to try to solve this, but it seems there is no simple way to go about it. I’ll keep hacking away, but if anyone has any suggestions, feel free to make an attempt.
Any thoughts on using cacheAsBitmap on the scrolled content, when scrolling?
Faster? Dangerous?
@Quentin — I’ve never used cacheAsBitmap, but I’ve been told it shouldn’t be used for objects that change often. Since this is a list of selectable options, it wouldn’t be ideal, but if it were a scrollable graphic, I’d use it.
Arpit posted something similar a while back:
http://www.arpitonline.com/blog/2009/08/02/optimized-list-a-pure-as3-list-with-renderer-recycling/
I’m not sure if he came up with a solution for the variable height rows though…
[...] my wildest expectations. One technique I used is improved item rendering. A few weeks back, I wrote a post about scrolling methods. The current one used in DestroyTwitter 1.7.2 uses preallocation and masking. It’s great for [...]