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

Что происходит в методе onLayout у View

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

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

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

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

Краткий ответ: Перераспределение позиций и размеров дочерних элементов

Метод onLayout() — это один из ключевых методов жизненного цикла отрисовки (layout pass) пользовательского интерфейса во фреймворке Android. Его основная задача — определить окончательное расположение (position) всех дочерних View (children) внутри родительской View, основываясь на ранее вычисленных размерах, которые были получены в фазе measurement (измерение).

Если упростить: onMeasure() отвечает на вопрос "Какого размера будут я и мои дети?", а onLayout()"Где именно (какие координаты) будут размещены мои дети внутри меня?".

Детальный механизм работы

Контекст: цепочка вызовов (Layout Pass)

  1. Система начинает проход с корневого View (обычно DecorView).
  2. Для каждого View вызывается measure() (который, в свою очередь, вызывает onMeasure()). Здесь вычисляются measuredWidth и measuredHeight для самого View и всех его дочерних элементов.
  3. После успешного измерения для каждого View вызывается layout() с параметрами l, t, r, b (left, top, right, bottom). Этот метод:
    *   Устанавливает эти координаты для самого `View` (сохраняя их в полях `mLeft`, `mTop`, `mRight`, `mBottom`).
    *   Вызывает **`onLayout()`**, где родительская View должна разместить своих детей.

Что происходит внутри onLayout()?

Это абстрактный метод в базовом классе View, поэтому каждая ViewGroup (например, LinearLayout, FrameLayout, RelativeLayout) обязана его реализовать. Его сигнатура:

protected void onLayout(boolean changed, int left, int top, int right, int bottom);
  • changed: Флаг, указывающий, изменились ли размеры или положение этого View с момента последнего вызова.
  • left, top, right, bottom: Относительные координаты этого View внутри его собственного родителя. Важно: это не абсолютные координаты на экране, а положение относительно родительского контейнера.

Внутри своей реализации onLayout() ViewGroup должна:

  1. Перебрать все дочерние View (например, в цикле for (int i = 0; i < getChildCount(); i++)).
  2. Для каждого ребенка определить его окончательные координаты. Алгоритм расчета этих координат — это и есть "магия" конкретного layout. Например:
    *   **`LinearLayout`:** Располагает детей последовательно (вертикально или горизонтально), учитывая вес (`weight`), гравитацию (`gravity`) и отступы (`margin`).
    *   **`FrameLayout`:** Обычно размещает всех детей в одном углу (чаще всего (0,0)), наслаивая их друг на друга, если не указана гравитация.
    *   **`RelativeLayout`:** Вычисляет положение каждого ребенка на основе сложных правил относительно других элементов (`layout_toRightOf`, `layout_alignParentTop`, и т.д.).
  1. Вызвать layout() для каждого дочернего View, передав ему рассчитанные координаты.

Пример упрощенной реализации onLayout() для горизонтального LinearLayout:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childLeft = getPaddingLeft(); // Начинаем с учёта padding родителя

    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);

        if (child.getVisibility() != GONE) {
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            // Рассчитываем положение для конкретного ребёнка
            int childTop = getPaddingTop() + ((b - t - getPaddingTop() - getPaddingBottom() - childHeight) / 2); // Центрирование по вертикали
            int childRight = childLeft + childWidth;

            // Ключевой момент: передаём координаты ребёнку
            child.layout(childLeft, childTop, childRight, childTop + childHeight);

            // Сдвигаем указатель для следующего ребёнка
            childLeft = childRight + child.getMarginRight();
        }
    }
}

Последствия вызова layout()

Когда для дочернего View вызывается layout(l, t, r, b), внутри него:

  • Сохраняются его границы.
  • Вызывается его собственный onLayout() (если это ViewGroup, и он должен разместить своих детей).
  • Если его положение или размер изменились, автоматически устанавливается флаг PFLAG_DRAWN, и планируется вызов onDraw() в следующем цикле отрисовки (draw pass).

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

  • Строгий порядок: measure() -> layout() -> draw(). Нельзя корректно расположить (layout) то, что не было измерено (measure).
  • Координаты относительны: Координаты в onLayout() всегда относительно родителя.
  • Обязанность родителя: Родительская ViewGroup полностью контролирует положение своих детей. Дочерняя View не может самостоятельно "решить", где ей находиться.
  • Оптимизация: Параметр changed помогает избежать лишней работы, если layout не изменился.
  • Кастомные ViewGroup: При создании собственных контейнеров реализация onLayout() — это основная и самая важная задача, где определяется визуальная логика компоновки.

Таким образом, onLayout() — это место, где абстрактные числа measuredWidth/Height превращаются в конкретное геометрическое расположение элементов на экране, формируя окончательную иерархию интерфейса.