← Назад к вопросам

Что произойдет если вызвать requestLayout()?

2.3 Middle🔥 191 комментариев
#UI и вёрстка#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Развернутый ответ на вопрос о методе requestLayout()

Вызов метода requestLayout() на объекте View в Android является сигналом системе о том, что текущие размеры и/или положение данного View (и потенциально его дочерних элементов) более не являются корректными и требуется заново выполнить этапы measure (измерение) и layout (размещение) в рамках следующего цикла отрисовки UI.

Что происходит внутри системы

Когда вы вызываете view.requestLayout(), запускается следующая цепочка событий:

  1. Пометка View как "требующей перерасчета": У View устанавливаются внутренние флаги PFLAG_FORCE_LAYOUT и PFLAG_INVALIDATED. Это метка для собственного родительского контейнера (ViewParent).

  2. Всплытие запроса наверх по иерархии: Запрос передается вверх по цепочке родителей с помощью ViewParent.requestLayout(). Этот процесс продолжается до самого корневого View (обычно DecorView). Важно понимать, что requestLayout() — это запрос, адресованный именно родительскому контейнеру, а не системе напрямую.

  3. Планирование прохода Traversal в корневом View: Корневой ViewRootImpl получает запрос и планирует выполнение полного цикла отрисовки (performTraversals()) для всего дерева View в рамках следующего синхронизационного импульса от дисплея (vsync).

  4. Выполнение performTraversals(): В запланированном цикле система последовательно выполняет три ключевых этапа:

    *   **Measure (Измерение)**: Определяет, какой размер должен иметь каждый View, учитывая его `MeasureSpec` (ограничения от родителя) и логику `onMeasure()`. Обход идет сверху вниз.
    *   **Layout (Размещение)**: Определяет фактическое положение и конечные размеры каждого View, вызывая его метод `onLayout()`. Здесь View, получив свои размеры, размещает собственные дочерние элементы (если они есть). Обход также сверху вниз.
    *   **Draw (Отрисовка)**: Если в процессе measure или layout размеры или положение View изменились, автоматически устанавливается флаг, инвалидирующий его область для отрисовки, что приводит к последующему вызову `onDraw()`.

Ключевые аспекты и отличия от invalidate()

  • requestLayout() vs invalidate(): Это фундаментальное различие.
    *   **`invalidate()`** помечает только **область отрисовки** View как невалидную, что приводит лишь к повторному вызову `onDraw()` на следующем цикле. Размеры и положение View не пересчитываются.
    *   **`requestLayout()`** запрашивает пересчет **размеров и положения** (measure + layout), что может, в свою очередь, привести к `invalidate()`, если эти параметры изменились.

  • Возможная каскадная перерисовка: Поскольку перемерка и переразмещение происходят сверху вниз, вызов requestLayout() на одном View может привести к тому, что весь путь до корня и, потенциально, многие другие View (братья, соседние ветви) также будут проходить через этапы measure и layout. Это может быть дорогостоящей операцией, если иерархия View глубокая или вызов происходит часто.

  • Оптимизации системы: Android пытается минимизировать издержки. Несколько последовательных вызовов requestLayout() между vsync-импульсами могут быть сгруппированы в один проход performTraversals(). Также, если родительский контейнер игнорирует флаг PFLAG_FORCE_LAYOUT (например, если ограничения MeasureSpec не изменились), повторный measure для этого конкретного View может быть пропущен.

Пример из практики

Рассмотрим кастомную View, размер которой зависит от внутреннего состояния.

class DynamicSizeView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private var dynamicWidth: Int = 100 // Начальная ширина в dp

    fun setDynamicWidth(newWidthPx: Int) {
        if (dynamicWidth != newWidthPx) {
            dynamicWidth = newWidthPx
            // Мы изменили параметр, ВЛИЯЮЩИЙ на размер View.
            // Простого invalidate() недостаточно, нужно запросить новый layout.
            requestLayout()
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // Наша логика измерения теперь использует dynamicWidth
        val desiredWidth = dynamicWidth + paddingLeft + paddingRight
        val desiredHeight = 200 // например, фиксированная высота

        val measuredWidth = resolveSize(desiredWidth, widthMeasureSpec)
        val measuredHeight = resolveSize(desiredHeight, heightMeasureSpec)

        setMeasuredDimension(measuredWidth, measuredHeight)
    }

    // onDraw() будет вызван автоматически, если изменились размеры после layout.
}

В этом примере setDynamicWidth() изменяет внутреннюю переменную, от которой зависит результат onMeasure(). Вызов requestLayout() критически необходим, чтобы уведомить родительский контейнер о необходимости предоставить новый MeasureSpec и перезапустить процесс измерения и размещения. Если бы мы вызвали только invalidate(), новые размеры бы не рассчитались, и внешний вид View не изменился бы корректно.

Заключение: requestLayout() — это мощный механизм для уведомления системы компоновки Android о необходимости обновить размеры и положение View. Его следует использовать осознанно, только когда изменения затрагивают логику onMeasure() или onLayout(), помня о потенциальных затратах на производительность из-за каскадного перерасчета.

Что произойдет если вызвать requestLayout()? | PrepBro