Extending ScrollControlBase: Getting Scrolling to Work
This post is the third page of a 4-post series on “Extending ScrollControlBase.” It discusses how to use ScrollControlBase’s setScrollBarProperties method and how to implement its scrollHandler event listener. Here’s some links to the other pages in this article:
1. Its Internals and Design Philosophy
2. Displaying the Scrollbars
3. Getting Scrolling to Work
4. The Workflow
Our component now has a functional implementation of UIComponent’s methods, but is still missing the ability to scroll. It turns out this requires two, separate, but equally important methods: scrollHandler and setScrollBarProperties. Lets talk them over in turn:
Working with setScrollBarProperties
This method is the brains behind ScrollControlBase. Every time your content changes sizes, this method will need to be called. The signature looks like this:
protected function setScrollBarProperties(
totalColumns:int, visibleColumns:int,
totalRows:int, visibleRows:int):void
Notice that the property names are pixel-independent. You simply describe how many rows your content has in total, and how many are visible, with the same for the columns. What a row or column is is dependent on your component. In many cases, these are pixels, but for some cases, a row could be a literal row of data as in a table, or a line of text. TextArea makes each rows a line of text, and each column a pixel, so your own rows and columns don’t necessarily have to be of the same unit. Calling this method will set the properties of the scrollbars as necessary to resize to the size of your data.
Our call is probably the simplest call this function can be, and is done at the end of updateDisplayList:
this.setScrollBarProperties(
this.currentImage.width,
unscaledWidth - edgeMetrics.left - edgeMetrics.right,
this.currentImage.height,
unscaledHeight - edgeMetrics.top - edgeMetrics.bottom
);
Remember that edgeMetrics is the size of the padding, borders, and viewed scrollbars. Our totalRows and totalColumns is simply the dimensions of our image. The visible rows and columns is the unscaled dimensions given to us as parameters, with the applicable edgeMetrics properties subtracted from each.
With this single call, the scroll bars will be properly shown or hidden, with the thumb (The selectable part) the right size to show all the content. You should call this whenever the size of your content changes, but since that’s usually coupled with a call to updateDisplayList, you’re safe to leave it only there in most cases. You shouldn’t call it in measure, though, since you won’t necessarily have correct unscaled dimensions yet.
Of course, proper scrollbars without event handlers are only eye candy. Let’s make them live.
Implementing scrollHandler
scrollHandler is an event listener implemented originally in ScrollControlBase. It receives updates whenever either scroll bar is moved. (It will even fire when liveScrolling is false, so handling whether this variable is set should be done inside the function). You should respond to scrolling events here.
Our implementation is mostly boilerplate:
override protected function scrollHandler(event:Event):void {
// Immediately return if there's no image.
if(!this.currentImage)
return;
// Return if it's not a ScrollEvent. This is for TextField scroll events bubbling up that
// we wouldn't understand.
if(!(event is ScrollEvent))
return;
// And finally, if we're not liveScrolling, and we're in the middle of scrolling, return.
if (!liveScrolling && ScrollEvent(event).detail == ScrollEventDetail.THUMB_TRACK)
return;
super.scrollHandler(event);
this.currentImage.x = this.viewMetrics.left + -this.horizontalScrollPosition;
this.currentImage.y = this.viewMetrics.top + -this.verticalScrollPosition;
}
There’s the three return if-statements. Each are commented, so I won’t mention them further here. The call to super.scrollHandler(event) will update the horizontal and vertical scroll positions stored inside ScrollControlBase.
Finally, the two calls move our image in the negative direction of the current position. These two lines are all it takes to implement scrolling in most cases, and with those two lines, you’ll have a functioning scrolling image.
(We add the viewMetrics left and top properties to keep our image flush with the edges of the border, otherwise we’d show a border-thickness worth of background when we’re at the bottom, and clip a border-thickness’ worth of image when we’re at the top.)
It might be asked why we don’t call invalidateDisplayList() instead inside scrollHandler, and ideally, that would be what we would do. But in this case, we’re safe to just update the values directly here, and not invoke the overhead associated with a full cycle of validation.
Read on to the next post to read the summary of what we’ve accomplished, along with a cheat sheet on how to implement basic scrolling in any of your components.