Я продолжаю сталкиваться с проблемой размеров и компоновки для пользовательских представлений, и мне интересно, может ли кто-нибудь предложить подход «лучших практик». Проблема заключается в следующем. Представьте себе пользовательское представление, в котором высота, необходимая для содержимого, зависит от ширины представления (аналогично многострочному TextView). (Очевидно, это применимо только в том случае, если высота не зафиксирована параметрами макета.) Загвоздка в том, что для заданной ширины довольно дорого вычислять высоту контента в этих пользовательских представлениях. В частности, вычисление в потоке пользовательского интерфейса слишком затратно, поэтому в какой-то момент необходимо запустить рабочий поток для вычисления макета, а когда он будет завершен, пользовательский интерфейс необходимо обновить.
Вопрос в том, как это должно быть оформлено? Я придумал несколько стратегий. Все они предполагают, что всякий раз, когда вычисляется высота, записывается соответствующая ширина.
Первая стратегия показана в этом коде:
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;
}
Когда поток макета завершается, он отправляет Runnable в поток пользовательского интерфейса, чтобы установить mLayoutHeight
в вычисленную высоту, а затем вызывает requestLayout()
(и invalidate()
).
Вторая стратегия заключается в том, чтобы onMeasure
всегда использовало текущее значение для mLayoutHeight
(без запуска потока компоновки). Тестирование изменений ширины и запуск потока компоновки можно выполнить, переопределив onSizeChanged
.
Третья стратегия состоит в том, чтобы быть ленивым и ждать запуска потока компоновки (при необходимости) в onDraw
.
Я хотел бы свести к минимуму количество запусков и/или уничтожений потока макета, а также как можно скорее вычислить требуемую высоту. Вероятно, было бы неплохо также минимизировать количество обращений к requestLayout()
.
Из документов ясно, что onMeasure
может вызываться несколько раз в течение одного макета. Менее ясно (но кажется вероятным), что onSizeChanged
также может вызываться несколько раз. Поэтому я думаю, что размещение логики в onDraw
может быть лучшей стратегией. Но это кажется противоречащим духу пользовательских размеров представления, поэтому у меня, по общему признанию, иррациональное предубеждение против этого.
Другие люди, должно быть, столкнулись с той же проблемой. Есть ли подходы, которые я пропустил? Есть ли лучший подход?