【问题标题】:Avoiding object allocations in onDraw() (StaticLayout)?避免 onDraw() (StaticLayout) 中的对象分配?
【发布时间】:2013-06-13 17:00:18
【问题描述】:

我有一个自定义视图,我很快学会了不要在其中进行对象分配,并将我所有的 Paint 分配移到不同的方法中,很好。

我需要对一些应用了一些可扩展“东西”的文本使用 StaticLayout。

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

    layoutBpm.draw(canvas);

这对我来说似乎是有道理的 onDraw 但我当然被警告要避免这种情况。对我来说,尽我所能避免任何性能问题对我来说很重要,因此我正在努力正确地做到这一点。

我似乎找不到任何我能理解的文档来解释 StaticLayout 的某些参数实际上做了什么(float spacingmult?float spacingadd?),但我认为第三个是 StaticLayout 文本的最大宽度我只能从 onMeasure 获得,因为它与我的画布大小有关。这让我想把任务放在 onMeasure 或 onDraw 中,我似乎都不打算这样做。可以将 StaticLayout 分配放在 onDraw 中还是有更好的方法来做到这一点?

希望这是有道理的,我对此很陌生。感谢您的任何帮助。

(编辑:我假设把这个赋值放在一个方法中并从 onDraw/onMeasure 调用只是很愚蠢,会阻止 Eclipse 警告我,但实际上并没有帮助?)

【问题讨论】:

    标签: android ondraw


    【解决方案1】:

    警告的原因是,您通常不需要在绘制操作中多次重新创建对象。与View 中的其他方法相比,onDraw() 可以被调用数百次。大多数时候,正在重新创建的对象是使用确切的参数重新创建的。其他时候,更改对象状态的开销比创建新对象要少。

    StaticLayout的情况下,您只需要在文本更改或调整填充、间距或最大宽度时创建一个新的。如果文本经常更改,那么您可能需要考虑DynamicLayout,它每次都会重新测量。与创建新对象相比,重新测量的开销更大,但不会比onDraw() 调用更频繁。

    如果文本不经常更改并且您绝对必须使用 StaticLayout,那么您可以使用这种结构。

    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);
    }
    

    基本上,您只会在发生变化时创建新布局。否则,您只需重用您创建的最后一个对象。

    EDIT:

    跳过测量通道的一种方法是在新调整大小的视图本身上调用View#measure。所以你的createLayout() 方法应该是这样的。

       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();
        }
    

    基本上,它的作用是告诉布局 View 可以达到的最大值。它会衡量自己和孩子。 forceLayout() 将在父级(您的自定义视图)上调用,它将根据新测量重新布局其他内容。

    我应该注意,我从来没有用 StaticLayout 做过这个,所以我不知道会发生什么。似乎在创建视图时可能已经处理了这种类型的测量,但可能没有。

    我希望这会有所帮助。

    【讨论】:

    • 谢谢,这让我有了一种全新的看待事物的方式。我在实现上述内容时遇到的唯一问题是textPaint 依赖于已经被调用的onMeasure()(文本根据画布大小缩放),textWidth 也是如此(它决定了文本何时换行),所以我不能在第一次查看的构造函数中做createLayout()。我可以把它放在onMeasure() 中,但我认为它并不经常被调用?
    • 文本实际上改变了很多,我正在处理的视图实际上是我制作的圆形SeekBar,StaticLayout用于中间的文本,与SeekBar的进度有关。因此,当您在圆圈周围拖动手指时,值会不断变化。我想虽然值变化很大,但长度总是填充到三位数,所以布局永远不需要调整大小,所以 StaticLayout 就可以了。我在静态/动态布局上找不到这么少的文档,所以我不确定这是否正确。再次感谢任何帮助。
    • 所以最后(我认为),我最终将onMeasure() 替换为onSizeChanged(),因为我的布局需求很简单,并将初始调用放在createLayout() 那里而不是onMeasure()。由于某种原因,Lint 似乎不介意 onSizeChanged() 中的对象分配,但不喜欢 onMeasure() 中的对象分配。我想这两种方法都被调用了相同的次数,所以不知道为什么其中一种方法很好,而另一种方法不行,但无论如何,它似乎还可以。我主要关心的是从onDraw() 中获得分配,我已经这样做了。再次感谢。
    • 我很高兴你设法让某些东西发挥作用,但这里有一些注意事项。 Lint 警告被编程为简单的常见错误。仅仅因为您没有收到警告并不意味着它是好的。另一方面,仅仅因为您收到警告并不意味着它很糟糕。这只不过是一个建议。 onSizeChanged() 实际上并没有被调用太多,因为它只会在 onMeasure() 中记录不同的测量 时被调用。另一方面, onMeasure 会为树中的每个视图调用,甚至可能更多。大小是否改变。
    • 可以在不通过 onMeasure() 传递的情况下重新测量布局。基本上,使用新的测量值,您在视图上调用View#measure(),视图本身将重新测量。然而,不同之处在于您的父视图中的其他元素将保持不变。如果这是一个问题,那么您需要调用 Layout pass 来重新调整视图中的其他元素。我已经用我的意思更新了答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-05
    • 1970-01-01
    相关资源
    最近更新 更多