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

Какие знаешь стадии отрисовки View?

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

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

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

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

Основные стадии отрисовки View в Android

Процесс отрисовки пользовательского интерфейса в Android состоит из трёх ключевых стадий, которые часто называют measure-layout-draw. Эти стадии выполняются последовательно для построения иерархии View, начиная с корневого элемента (обычно DecorView).

1. Measure (Измерение)

На этой стадии система определяет размеры и требования к измерению для каждого View. Процесс проходит сверху вниз: родительские View запрашивают измерения у своих дочерних элементов.

Ключевые моменты:

  • Вызывается метод onMeasure(int widthMeasureSpec, int heightMeasureSpec).
  • MeasureSpec — это комбинация режима измерения (EXACTLY, AT_MOST, UNSPECIFIED) и размера.
  • View должен установить свои итоговые размеры через setMeasuredDimension(int measuredWidth, int measuredHeight).
  • Важно: измеренные размеры (getMeasuredWidth/Height) могут отличаться от окончательных отрисованных размеров (getWidth/Height), если вторая стадия (layout) изменит их.

Пример простого onMeasure:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // 1. Извлекаем режим и размер из MeasureSpec
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    
    // 2. Вычисляем желаемый размер (например, размер содержимого)
    val desiredWidth = calculateDesiredWidth() 
    
    // 3. Определяем итоговую ширину в зависимости от режима
    val finalWidth = when (widthMode) {
        MeasureSpec.EXACTLY -> widthSize // Точный размер задан родителем
        MeasureSpec.AT_MOST -> desiredWidth.coerceAtMost(widthSize) // Не больше заданного
        MeasureSpec.UNSPECIFIED -> desiredWidth // Любой размер
        else -> desiredWidth
    }
    
    // Аналогичные вычисления для высоты...
    val finalHeight = calculateHeight(heightMeasureSpec)
    
    // 4. Обязательно устанавливаем измеренные размеры
    setMeasuredDimension(finalWidth, finalHeight)
}

2. Layout (Компоновка)

На этой стадии система определяет финальное положение каждого View на экране. Родительские View размещают свои дочерние элементы, используя размеры, полученные на стадии measure.

Ключевые моменты:

  • Вызывается метод onLayout(boolean changed, int left, int top, int right, int bottom).
  • Для ViewGroup (контейнеров) необходимо в этом методе вручную вызвать layout() для каждого дочернего элемента, передав вычисленные координаты.
  • Координаты left, top, right, bottom задаются относительно родителя.

Пример onLayout для простого вертикального контейнера:

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    var currentTop = paddingTop
    
    for (i in 0 until childCount) {
        val child = getChildAt(i)
        
        if (child.visibility != GONE) {
            val childWidth = child.measuredWidth
            val childHeight = child.measuredHeight
            
            // Размещаем ребенка, выравнивая по левому краю с отступом
            child.layout(
                paddingLeft, // left
                currentTop, // top
                paddingLeft + childWidth, // right
                currentTop + childHeight // bottom
            )
            currentTop += childHeight + spacing // Увеличиваем позицию для следующего
        }
    }
}

3. Draw (Отрисовка)

Финальная стадия, на которой происходит непосредственное рисование View на Canvas. Здесь определяется визуальное содержимое: цвет фона, текст, изображения, кастомная графика.

Ключевые моменты:

  • Вызывается метод onDraw(Canvas canvas).
  • Система передаёт готовый Canvas, на котором можно выполнять рисование.
  • Важно: не вызывайте onDraw напрямую. Для запроса перерисовки используйте invalidate() (для обновления всего View) или postInvalidate() (из не-UI потока).
  • Стадия draw также включает в себя dispatchDraw для ViewGroup, чтобы отрисовать дочерние элементы (они рисуются после родителя по умолчанию).

Пример кастомного onDraw:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas) // Важно: рисуем стандартное содержимое (фон и т.д.)
    
    // Рисуем прямоугольник
    paint.color = Color.RED
    canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
    
    // Рисуем текст
    paint.color = Color.WHITE
    paint.textSize = 40f
    canvas.drawText("Hello", 50f, 50f, paint)
    
    // Рисуем произвольный путь
    val path = Path().apply {
        moveTo(10f, 10f)
        lineTo(200f, 150f)
    }
    canvas.drawPath(path, paint)
}

Дополнительные важные аспекты

  • Оптимизация: Система пытается избегать полного прохода по всем стадиям. Например, requestLayout() запускает measure и layout, а invalidate() — только draw. Однако изменение размеров View обычно ведёт ко всем трём стадиям.
  • Производительность: Глубокие иерархии View и сложная логика в onMeasure/onLayout могут серьёзно ударить по производительности. Используйте иерархию просмотра (Layout Inspector) и профилировщик GPU для отладки.
  • Отличия getWidth()/getHeight() от getMeasuredWidth()/getMeasuredHeight(): Первые возвращают финальные размеры после layout, вторые — размеры после measure. В onDraw и при взаимодействии всегда используйте getWidth()/getHeight().

Понимание этих стадий критически важно для создания кастомных View и оптимизации производительности UI, так как позволяет контролировать процесс измерения, расположения и отрисовки элементов интерфейса.

Какие знаешь стадии отрисовки View? | PrepBro