0

Imagine a tablet app that displays two content areas side by side. They completely fill the display, so are 100% in height and 50% in width. Lets assume we add a list to one container. Naturally this list will consume half the space of the whole display.

Now to my problem, is it possible that high framerate scrolling is kind of impossible with lists of this size? I've got the most basic AS3 ItemRenderer and still can't get anything higher than 30fps during scrolling. Now the odd part, if I add stuff the other container, lets say another list or other components, the list scrolling performance drops to the low 20s. So nowhere near the 40+ fps you see Adobe advertising in their MAX shows.

I'm testing on a iPad2 and 3 and even with static values, scrolling isn't really good. Now if I enable streaming values so that the ItemRenderer's set data method is called, the framerate drops another 2 to 3 frames.

My (almost) complete renderer looks like this, but even if I strip it to just display a single textfield, disable the stuff going on in the set data and also set only the size of the single textfield in the layoutContents, performance is as described, about 30 if the list is displayed alone, low 20s if other stuff is displayed as well.

    //FCStyleableTextField is just a StyleableTextField with an additional ID

    private var _textFields:Vector.<FCStyleableTextField>;
    private var _oldValues:Dictionary;
    private var _sym:Symbol;

    public function GridRenderer() {
        super();
        _textFields = new Vector.<FCStyleableTextField>();
        _oldValues = new Dictionary();
    }

    override protected function createChildren():void {

        var _symLabel:FCStyleableTextField = new FCStyleableTextField();
        _symLabel.editable = false;
        _symLabel.selectable = false;
        _symLabel.multiline = false;
        _symLabel.id="sym";
        _symLabel.setStyle("fontSize", fontSize);
        _symLabel.textColor = 0xc0c0c0;
        _textFields.push(_symLabel);
        addChild(_symLabel);

        var fidLen:int = fids.length;
        for (var i:int = 0; i<fidLen; i++) {
            var _fid_lbl:FCStyleableTextField = new FCStyleableTextField();
            _fid_lbl.selectable = false;
            _fid_lbl.editable = false;
            _fid_lbl.multiline = false;
            _fid_lbl.id = String(fids[i]);
            _fid_lbl.textColor = 0xc0c0c0;
            _fid_lbl.setStyle("textAlign", "right");
            _fid_lbl.setStyle("fontSize", fontSize);
            _fid_lbl.text = " ";
            _textFields.push(_fid_lbl);
            addChild(_fid_lbl);

            if(i>visibleColumns) {
                _fid_lbl.includeInLayout = false;
                _fid_lbl.visible = false;
            }
        }
    }


    override public function set data(value:Object):void {
        if(!value) return;

        if(data) {
            // check if the value's symbolName is different than the current
            // data's symbolName, if so, the itemRenderer has been 
            // recycled, thus we need to reset the old fid values
            if((value as Symbol).symbolName != (data as Symbol).symbolName)
                _oldValues = new Dictionary();
        }

        super.data = value;

        _sym = data as Symbol;

        try {
            var textLen:int = _textFields.length;
            for (var i:int = 0; i<textLen;i++) {
                var lbl:FCStyleableTextField = _textFields[i];
                if(lbl.id == "sym") {
                    lbl.text = _sym.symbolName;
                    lbl.truncateToFit();
                } else {
                    if(lbl.id == _sym.fidList.fidMap[lbl.id].fidId && lbl.text != _sym.fidList.fidMap[lbl.id].fieldValue) {
                        var time:int = new Date().time;
                        var timerName:String = _sym.symbolName+","+lbl.id+","+grid;
                        globalTimer.addTimer(timerName, time, "reset", lbl, null, null);

                        var _oldVal:* = _oldValues[lbl.id];
                        var _newVal:* = _sym.fidList.fidMap[lbl.id].fieldValue;

                        // basic color formatting
                        if(Number(_newVal) > Number(_oldVal))
                            lbl.textColor = 0x40c040;
                        else if(Number(_newVal) < Number(_oldVal))
                            lbl.textColor = 0xf05050;

                        // add + to change and changePercent fids if value is positive
                        if(lbl.id == "56") {
                            if(_newVal >0)
                                lbl.text = "+" + _newVal;
                            else
                                lbl.text = String(_newVal);
                        } else if(lbl.id == "11") {
                            if(_newVal >0)
                                lbl.text = "+" + _newVal;
                            else 
                                lbl.text = String(_newVal);
                        } else 
                            lbl.text = String(_newVal);

                        if(!_sym.fidList.fidMap[lbl.id].fieldValue)
                            lbl.text =" ";

                        _oldValues[lbl.id] = _newVal;
                    } 
                }

                lbl.truncateToFit();
            }       
        } catch (e:Error) { /* nothing to do here -> try/catch required due to async symbolassembly */ }
    } 

    override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void {
        var viewWidth:Number  = unscaledWidth  - paddingLeft - paddingRight;
        var viewHeight:Number = unscaledHeight - paddingTop  - paddingBottom;
        var _previousLabel:FCStyleableTextField;
        var textLen:int = _textFields.length;

        for(var i:int =0; i<textLen;i++) {
            var lbl:FCStyleableTextField = _textFields[i];
            graphics.beginFill(0x808080, .3);
            lbl.height = viewHeight;
            lbl.y = paddingTop;

            if(lbl.id=="sym") {
                lbl.width = 95;
            } else if (lbl.id == "35000") {
                lbl.width = 24;
            } else { 
                lbl.width = optimalColWidth;
            }

            _previousLabel ? lbl.x = (_previousLabel.x + _previousLabel.width): lbl.x = paddingLeft;
            graphics.drawRect(lbl.x+lbl.width, 1, 1, unscaledHeight-1);

            lbl.commitStyles();
            _previousLabel = lbl;
            graphics.endFill();
        }
    }

Still, I'm pretty sure that it is not the item renderer that causes the slowdown, cause as I said, it costs 2, maybe 3 frames compared to a renderer that displays just a single textfield. I rather think that Flex somehow can't handle the amount of vectors being displayed at once, is something like that possible? And is there any way to boost performance? I already disabled live streaming values as soon as the user scrolls the list, so that flex basically just has to scroll bitmaps (since LabelItemRenderer automatically enables cacheasbitmap), but that gained maybe 4 frames. What are your guys tricks to make scrolling a little smoother?

AlBirdie
  • 2,071
  • 1
  • 25
  • 45
  • http://evtimmy.com/2011/10/424/ - have you checked that presentation? – Alexander Farber Jul 18 '12 at 15:10
  • I'm not sure if this question is concretely answerable. How are you evaluating frame rates on the scrolling? – JeffryHouser Jul 18 '12 at 15:16
  • @AlexanderFarber, yes I have. That are the slides from the MAX show I was talking about. Basically nothing new there, and nothing I didn't already consider (and implement) in the renderer, such as caching. – AlBirdie Jul 18 '12 at 15:23
  • @www.Flextras.com, I'm measuring performance with a fps monitor. I'm not exactly sure if there is only one answer to this question. As I said, not sure what is going on here, just a bit shocked by the frames I'm getting for very basic stuff. – AlBirdie Jul 18 '12 at 15:27
  • The only other "insight" I may have to add is that debug builds supposedly run much slower on iOS than Release Builds. I've seen this myself. – JeffryHouser Jul 18 '12 at 15:31
  • :) Debug builds get me less than 5fps. Actually, I do nothing but release builds on iOS devices. So just to confirm this, the item renderer as showed above is pretty much as optimized as it gets, right? Cause I can stop messing around with it then and look elsewhere for the missing frames. ;) – AlBirdie Jul 18 '12 at 15:40
  • 1
    @Al_Birdy what is an "fps monitor"? I wondered how FPS were measured when reading that presentation. – Alexander Farber Jul 18 '12 at 18:23
  • I don't know what FCStyleableTextField or Symbol are; so it's tough to tell how optimized your code is. I don't understand why you appear to be storing old values. It looks like your layoutContents is drawing a fill on the full itemRenderer for each element of your _textFields Vector. Since the color of the fill is hard coded, can you get away with drawing a single one? – JeffryHouser Jul 18 '12 at 21:06
  • As I am sure you know the set data method is called anytime you scroll the list and as such needs to be as small as possible. I would never put a loop in there. Just a thought here but break the loop and some of the other code out into another function and call that function on a 1 shot timer. Each time set data is entered refresh the timer. You can set the timer to a fast time like say 10ms – The_asMan Jul 19 '12 at 01:14
  • This should give you much better response since a ton of code is not processed when it shouldn't be. However with this method you wont see the updates until scrolling is slowed or stopped. So you have some pros and cons to consider. – The_asMan Jul 19 '12 at 01:19
  • @www.Flextras.com, FCStyleableTextField is (as commented in the code) a subclass of StyleableTextfield with an additional ID. Symbol is a model that consists of a String with a name and a Dictionary that stores values. The layoutContents just draws a single line after each TextField, which makes the Renderer look like a grid. It is therefore hard to make a single drawing since I don't know the sizes of the TextFields beforehand. I store the old values so that I can format the colors of the text. Green for a greater value, red for a smaller one. – AlBirdie Jul 19 '12 at 08:17
  • @The_asMan, I know the loop is far from optimal, let me see what I can do about that. The weird thing is, even without the loop in the set data, with just setting the text of a single label, I still don't get considerably higher frame rates. – AlBirdie Jul 19 '12 at 08:23
  • @Al_Birdy one step at time :) – The_asMan Jul 19 '12 at 14:41
  • @The_asMan, as usual ;). – AlBirdie Jul 19 '12 at 15:00

1 Answers1

1

Figured out that using setElementSize() and setElementPosition() instead of using width/height and x/y makes quite a difference. Gained 3fps in initial scrolling performance and 8fps once every item has been rendered. So I'm pretty close to 30fps now, still not close to what you can do with a native app, but I figure that's as good as it gets with Flex and such a massive renderer.

Also disabled live updates so that the renderer doesn't need to be cached as a bitmap again when updates come in.

AlBirdie
  • 2,071
  • 1
  • 25
  • 45