Thursday, November 27, 2008

Smooth-Scrolling Part 3 - The Sliding Window

I finally figured out the true nature of the problem (described in parts 1 and 2), but the prognosis was still the same -- rendering the entire list to a large bitmap caused an OutOfMemory exception for some devices. I could not pre-render everything to ensure smooth scrolling.

I spent some time investigating hacks to work-around it, like declaring a non-graphics block of memory to hold the large bitmap, but every one of them failed or was too much work to be practical. And when I sought out advice from development communities, the reaction was mostly that I was foolish to try something like this with WM.

I finally settled on a "sliding window" approach. The screen can hold 4 or 5 items at most. Instead of rendering all 50 or so items to a single bitmap, why not render the 5 items on screen and a few of the ones before and after that set? This way there's a bit pre-rendered buffer when the user scrolls up or down. When the user stops scrolling, I can re-calculate the items that need to be in the buffer and re-render it in a background thread.

The core concept is simple, but there's a bit more in the actual code. Deciding on the window size was difficult. 15 items was ok for most users, but still crashed a few VGA users. And ironically, QVGA devices could handle WAY more items because of their smaller resolution. I liked that my Mogul could scroll the entire set without buffering and wanted to keep it that way. So the first step in the code is to determine the number of items it can handle in the buffer. I do it by starting with the maximum number of statuses in the list, then try to create a bitmap that will fit that number. If that fails, it decreases the number and tries again until it works.

The code that re-fills the buffer after sliding is interesting too. After the UI has stopped scrolling, it determines the statuses on-screen and enough before and after to fill the buffer. It passes that whole list to the buffer. At first, the buffer would just render that whole list again, but this was inefficient. Now it determines if there is any overlap with what it currently has rendered. It moves the overlap to the top (or bottom) of the buffer, then only re-renders the new items.

One downside to this is that it is possible for the user to scroll faster than the buffer can re-fill itself. When they do, they'll see a blank area momentarily until the buffer is able to catch up again. And how often this happens depends on how small the buffer has been made.

No comments: