Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding object allocations in onDraw() (StaticLayout)?

Tags:

android

ondraw

I have a custom view which I quickly learned to not do object allocations in and moved all my Paint allocations into a different method, fine.

I need to use a StaticLayout however for some text that had some spannable 'stuff' applied.

    layoutBpm = new StaticLayout(bpmValue,
            textPaintBPM, arcRadius, Layout.Alignment.ALIGN_NORMAL, 0, 1,
            false);

    layoutBpm.draw(canvas);

This seemed to make sense to me to have in onDraw but I am of course getting warned to avoid this. It is important to me that I do everything I can to avoid any performance issues so I am trying to do this properly.

I can't seem to find any documentation I can understand that explains what some of the parameters to StaticLayout actually do (float spacingmult? float spacingadd?), but the third one there I think is maximum width for the StaticLayout text which I can only get from onMeasure as it relates to my canvas size. This leaves me wanting to put the assignment in onMeasure or onDraw, neither of which it seems I am meant to do. Is it okay to put StaticLayout assignment in onDraw or is there a better way to do this?

Hope this makes sense, I am very new to this. Thank you for any help.

(edit: I assume putting this assignment in a method and calling from onDraw/onMeasure is just being silly and will stop Eclipse warning me but wont actually help?)

like image 997
Anthony Avatar asked Jun 13 '13 17:06

Anthony


1 Answers

The reason for the warning is because, very often, you do not need to recreate an object multiple times in a draw operation. onDraw() could be called hundreds of times compared to other methods in a View. Most of the time, the objects being recreated are being recreated with the exact parameters. Other times, it's simply less overhead to change an object's state than it is to create a new object.

In the case of a StaticLayout, you only need to create a new one when the text changes or when you adjust the padding, spacing, or maxwidth. If the text changes often, then you may want to consider DynamicLayout which will remeasure itself every time. Remeasuring costs more overhead than creating a new object, but there's no way it's happening more often than onDraw() calls.

If the text doesn't change often and you absolutely MUST use a StaticLayout, then you can get away with something like this structure.

StaticLayout myLayout;
String textSource = defaultSource;
Paint textPaint = defaultPaint;
int textWidth = defaultWidth; 

public CustomView(Context ctx) {
    super(ctx);
    createLayout();
}

public void setText(String text) {
   textSource = text;
   createLayout();
}

public void setWidth(int width) {
   textWidth = width;
   createLayout();
}

@Override
public void onDraw(Canvas canvas) {
   myLayout.draw(canvas);
}

private void createLayout() {
   myLayout = new StaticLayout(textSource, textPaint, textWidth, Layout.Alignment.ALIGN_NORMAL, 0, 1, false);
}

Basically, you only create a new layout when something changes. Else you just reuse the last object you created.

EDIT:

One way to skip a measure pass is to call View#measure on the newly resized view itself. So your createLayout() method would be something like this.

   private void createLayout() {
       myLayout = new StaticLayout(textSource, textPaint, textWidth, Layout.Alignment.ALIGN_NORMAL, 0, 1, false);
       int textWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
       int textHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
       myLayout.measure(textWidthSpec, textHeightSpec);
       forceLayout();
    }

Basically what this does is tells the layout the maximum the View can be. It will measure itself and it's children. The forceLayout() will be invoked on the parent (your custom view) which will re-layout the other contents based on the new measurements.

I should note that I have never done this with a StaticLayout so I have no idea what will happen. It seems like this type of measurement might already be handled when the View is created, but maybe not.

I hope this helps.

like image 159
DeeV Avatar answered Oct 14 '22 07:10

DeeV