I keep running into a sizing and layout problem for custom views and I'm wondering if anyone can suggest a "best practices" approach. The problem is as follows. Imagine a custom view where the height required for the content depends on the width of the view (similar to a multi-line TextView). (Obviously, this only applies if the height isn't fixed by the layout parameters.) The catch is that for a given width, it's rather expensive to compute the content height in these custom views. In particular, it's too expensive to be computed on the UI thread, so at some point a worker thread needs to be fired up to compute the layout and when it is finished, the UI needs to be updated.
The question is, how should this be designed? I've thought of several strategies. They all assume that whenever the height is calculated, the corresponding width is recorded.
The first strategy is shown in this code:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureWidth(widthMeasureSpec);
setMeasuredDimension(width, measureHeight(heightMeasureSpec, width));
}
private int measureWidth(int widthMeasureSpec) {
// irrelevant to this problem
}
private int measureHeight(int heightMeasureSpec, int width) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
if (width != mLastWidth) {
interruptAnyExistingLayoutThread();
mLastWidth = width;
mLayoutHeight = DEFAULT_HEIGHT;
startNewLayoutThread();
}
result = mLayoutHeight;
if (specMode == MeasureSpec.AT_MOST && result > specSize) {
result = specSize;
}
}
return result;
}
When the layout thread finishes, it posts a Runnable to the UI thread to set mLayoutHeight
to the calculated height and then call requestLayout()
(and invalidate()
).
A second strategy is to have onMeasure
always use the then-current value for mLayoutHeight
(without firing up a layout thread). Testing for changes in width and firing up a layout thread would be done by overriding onSizeChanged
.
A third strategy is to be lazy and wait to fire up the layout thread (if necessary) in onDraw
.
I would like to minimize the number of times a layout thread is launched and/or killed, while also calculating the required height as soon as possible. It would probably be good to minimize the number of calls to requestLayout()
as well.
From the docs, it's clear that onMeasure
might be called several times during the course of a single layout. It's less clear (but seems likely) that onSizeChanged
might also be called several times. So I'm thinking that putting the logic in onDraw
might be the better strategy. But that seems contrary to the spirit of custom view sizing, so I have an admittedly irrational bias against it.
Other people must have faced this same problem. Are there approaches I've missed? Is there a best approach?