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

Что нужно чтобы вызвался onLayout у View?

2.0 Middle🔥 131 комментариев
#UI и вёрстка

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

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

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

Механизм вызова onLayout() у View

Чтобы понять, что вызывает onLayout(), нужно рассмотреть весь процесс лэйаута в иерархии View. onLayout() — это защищённый метод, который вызывается, когда родительский контейнер определяет положение и размеры своих дочерних View.

Основные условия вызова onLayout():

1. Изменение размеров или иерархии View

  • Добавление/удаление дочерних View
  • Изменение видимости View (VISIBLE, INVISIBLE, GONE)
  • Изменение размеров View (через LayoutParams)
  • Изменение контента, влияющее на размеры (текст, изображения)

2. Запрос перерисовки лэйаута

// Эти методы запрашивают пересчет лэйаута
view.requestLayout()
view.invalidate()
view.forceLayout()

// Для всей иерархии
view.post {
    view.requestLayout()
}

3. Системные события

  • Изменение конфигурации (поворот экрана)
  • Изменение размера окна (multi-window mode)
  • Изменение системных настроек (размер шрифта)

Как работает процесс:

// Пример структуры вызовов в ViewGroup
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    // Родитель размещает дочерние элементы
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        
        // Вычисляем положение дочернего элемента
        int childLeft = ...;
        int childTop = ...;
        int childRight = ...;
        int childBottom = ...;
        
        // Вызываем layout() у дочернего элемента
        child.layout(childLeft, childTop, childRight, childBottom);
    }
}

Детальный механизм:

Шаги, приводящие к вызову onLayout():

  1. requestLayout() устанавливает флаги PFLAG_FORCE_LAYOUT у View и всех родителей до корня
  2. ViewRootImpl планирует выполнение performTraversals() в следующем цикле сообщений
  3. В performTraversals() вызывается performLayout():
    private void performLayout() {
        // Вычисляются размеры и положение
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }
    
  4. View.layout() проверяет необходимость перерисовки:
    public void layout(int l, int t, int r, int b) {
        boolean changed = setFrame(l, t, r, b); // Изменились ли границы?
        
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b); // Вот здесь вызывается наш метод!
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        }
    }
    

Важные особенности:

Для View (не-Group):

  • onLayout() — пустая реализация, так как у обычной View нет дочерних элементов
  • Вызывается, но не делает ничего значимого

Для ViewGroup:

  • Должен быть обязательно переопределён
  • Определяет положение всех дочерних View
  • Вызывает child.layout() для каждого дочернего элемента

Отличие от onMeasure():

// onMeasure() определяет РАЗМЕРЫ
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // Вычисляем желаемые размеры
    setMeasuredDimension(calculatedWidth, calculatedHeight)
}

// onLayout() определяет ПОЛОЖЕНИЕ
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    // Располагаем дочерние элементы
    child.layout(childLeft, childTop, childRight, childBottom)
}

Практический пример кастомного ViewGroup:

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

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 1. Сначала измеряем детей
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        
        // 2. Определяем свои размеры
        val width = View.getDefaultSize(suggestedMinimumWidth, widthMeasureSpec)
        val height = View.getDefaultSize(suggestedMinimumHeight, heightMeasureSpec)
        setMeasuredDimension(width, height)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        // Этот метод вызовется автоматически после onMeasure()
        // когда система решит, что нужно перерасположить элементы
        
        var currentTop = t
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            if (child.visibility != GONE) {
                // Размещаем ребенка вертикально друг под другом
                child.layout(
                    l, 
                    currentTop, 
                    l + child.measuredWidth, 
                    currentTop + child.measuredHeight
                )
                currentTop += child.measuredHeight
            }
        }
    }
}

Ключевые моменты для запоминания:

  • onLayout() вызывается автоматически системой при необходимости
  • Не вызывайте onLayout() напрямую — используйте requestLayout()
  • Метод получает уже вычисленные границы View
  • Для ViewGroup обязательно переопределять onLayout() для размещения детей
  • Процесс лэйаута всегда следует за процессом измерения (measure → layout → draw)
  • Изменение размеров или положения View внутри onLayout() может привести к бесконечному циклу

Понимание механизма вызова onLayout() критически важно для создания корректно работающих кастомных ViewGroup и оптимизации производительности UI.