Я постоянно сталкиваюсь с проблемой калибровки и макета для пользовательских представлений, и мне интересно, может ли кто-нибудь предложить подход "наилучшей практики". Проблема заключается в следующем. Представьте себе пользовательский вид, где высота, требуемая для контента, зависит от ширины представления (аналогично многострочному 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
всегда использовала значение then-current для mLayoutHeight
(без запуска потока макета). Тестирование изменений в ширине и запуск потока макета будет выполняться путем переопределения onSizeChanged
.
Третья стратегия должна быть ленивой и ждать, чтобы запустить поток макета (при необходимости) в onDraw
.
Я хотел бы свести к минимуму количество раз, когда поток макета запускается и/или убивается, а также как можно скорее вычисляет требуемую высоту. Вероятно, было бы полезно минимизировать количество вызовов на requestLayout()
.
В документах ясно, что onMeasure
может вызываться несколько раз в течение одного макета. Он менее ясен (но кажется вероятным), что onSizeChanged
также можно было бы назвать несколько раз. Поэтому я думаю, что логика в onDraw
может быть лучшей стратегией. Но это кажется противоречащим духу индивидуального определения размеров, поэтому у меня есть, по общему признанию, иррациональное предвзятое отношение к нему.
Другие люди должны были столкнуться с этой же проблемой. Есть ли подходы, которые я пропустил? Есть ли лучший подход?