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

Какие знаешь методы жизненного цикла View?

1.0 Junior🔥 251 комментариев
#UI и вёрстка

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

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

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

Жизненный цикл View в Android

Понимание lifecycle методов View критически важно для оптимизации производительности и избежания утечек памяти. Давай разберём все этапы в порядке вызова.

Основная последовательность

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

1. Constructor — Конструктор

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    // Инициализация
    init {
        // Парсим кастомные атрибуты
        val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
        val customColor = ta.getColor(R.styleable.CustomView_customColor, Color.BLACK)
        ta.recycle()
    }
}

Когда вызывается: Сразу при создании View объекта

Что делать здесь:

  • Инициализация полей
  • Парсинг кастомных атрибутов
  • Создание Paint, Bitmap и других дорогих объектов (НО лучше позже)

Что НЕ делать:

  • Не обращаться к размерам View (они ещё не известны)
  • Не стартовать animations
  • Не создавать слишком много объектов

2. onAttachedToWindow() — Присоединение к окну

override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    
    // View теперь часть Activity window
    // Можем получить размеры родителя
    val parent = parent as? ViewGroup
    
    // Регистрируем слушатели
    registerPhoneStateListener()
    
    // Запускаем анимации
    animatorSet.start()
}

Когда вызывается: Когда View добавлена в ViewGroup и связана с Activity Window

Что делать здесь:

  • Регистрировать broadcast receiver
  • Регистрировать слушатели (location, sensor и т.д.)
  • Запускать animations
  • Начинать мониторинг батареи/сети

Важно: Всегда вызови super.onAttachedToWindow()

3. onMeasure() — Измерение размеров

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // Парсим spec
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    
    // Вычисляем желаемый размер
    val desiredWidth = 200.dpToPx()
    val desiredHeight = 300.dpToPx()
    
    // Выбираем финальный размер в зависимости от mode
    val width = when (widthMode) {
        MeasureSpec.EXACTLY -> widthSize
        MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
        else -> desiredWidth
    }
    
    val height = when (heightMode) {
        MeasureSpec.EXACTLY -> heightSize
        MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
        else -> desiredHeight
    }
    
    // ОБЯЗАТЕЛЬНО вызываем setMeasuredDimension
    setMeasuredDimension(width, height)
}

Когда вызывается: Несколько раз при layout pass. Может вызваться много раз!

Три режима MeasureSpec:

  • EXACTLY — точный размер (match_parent или конкретное число)
  • AT_MOST — максимум (wrap_content)
  • UNSPECIFIED — не ограничено (редко)

Что делать здесь:

  • Вычислить желаемый размер
  • Вызвать setMeasuredDimension() в конце
  • Учесть padding и margin

Что НЕ делать:

  • Не создавать объекты (вызывается часто)
  • Не выполнять сложные расчёты
  • Не забывать вызвать setMeasuredDimension()

4. onLayout() — Расположение на экране

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)
    
    // changed = true если изменились координаты/размер
    if (changed) {
        // Пересчитаем позиции дочерних View
        val childCount = childCount
        var currentY = paddingTop
        
        for (i in 0 until childCount) {
            val child = getChildAt(i) ?: continue
            val childHeight = child.measuredHeight
            
            // Размещаем дочернюю View
            child.layout(
                paddingLeft,
                currentY,
                width - paddingRight,
                currentY + childHeight
            )
            
            currentY += childHeight + spacing
        }
    }
}

Когда вызывается: После onMeasure(), перед onDraw()

Параметры:

  • changed: true если View изменила размер/позицию
  • left, top, right, bottom: новые координаты

Что делать здесь:

  • Разместить дочерние View (для ViewGroup)
  • Пересчитать внутренние параметры
  • Это последний момент для расчётов перед отрисовкой

5. onDraw() — Отрисовка

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    
    // Этот метод вызывается часто, оптимизируй!
    
    // Рисуем фон
    canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), backgroundPaint)
    
    // Рисуем текст
    canvas.drawText(
        "Hello",
        (width / 2).toFloat(),
        (height / 2).toFloat(),
        textPaint
    )
    
    // Рисуем круг
    canvas.drawCircle(
        (width / 2).toFloat(),
        (height / 2).toFloat(),
        radius.toFloat(),
        circlePaint
    )
}

Когда вызывается: Очень часто, на каждый frame (60 fps = 60 раз в секунду)

Правила оптимизации:

  • НЕ создавай новые объекты (Paint, Rect и т.д.)
  • НЕ выполняй сложные расчёты
  • Кэшируй все вычисления в onLayout()
  • Рисуй максимально просто

Плохо:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // ПЛОХО — создаёт новый объект каждый frame
    val paint = Paint().apply { color = Color.RED }
    canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), paint)
}

Хорошо:

private val circlePaint = Paint().apply { color = Color.RED }

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // ХОРОШО — переиспользуем Paint
    canvas.drawCircle(centerX.toFloat(), centerY.toFloat(), radius.toFloat(), circlePaint)
}

6. onSizeChanged() — Изменение размеров

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    
    // Вызывается когда размер View изменился
    if (w != oldw || h != oldh) {
        // Пересчитываем всё, что зависит от размеров
        updateLayoutParams(w, h)
        invalidate()  // Перерисовать
    }
}

Когда вызывается: После первого onLayout() и каждый раз при изменении размера

7. invalidate() и requestLayout() — Инвалидация

// Перерисовать (вызывает onDraw)
invalidate()  // Всю View
invalidateRect(rect)  // Часть View

// Пересчитать размер (вызывает onMeasure + onLayout + onDraw)
requestLayout()

// Правило: используй правильный метод
private fun updateColor(newColor: Int) {
    textPaint.color = newColor
    invalidate()  // Достаточно перерисовать
}

private fun updateText(newText: String) {
    text = newText
    // requestLayout() если изменился размер
    // invalidate() если просто нужна перерисовка
    requestLayout()  // В этом случае текст может иметь другой размер
}

8. onDetachedFromWindow() — Отсоединение от окна

override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    
    // View больше не видна пользователю
    // Надо очистить ресурсы
    
    // Останавливаем анимации
    animatorSet.cancel()
    
    // Отписываемся от слушателей
    unregisterPhoneStateListener()
    
    // Отписываемся от broadcast receiver
    LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)
    
    // Отменяем задачи
    handler.removeCallbacksAndMessages(null)
    
    // Очищаем bitmap
    bitmap?.recycle()
}

Когда вызывается: Когда View удалена из ViewGroup или Activity закрывается

Критически важно:

  • Всегда отписывайся от слушателей
  • Отменяй animator/animation
  • Очищай ресурсы (bitmap, cursor и т.д.)
  • Отменяй handler callbacks

Это предотвратит утечки памяти!

Полная последовательность

1. Constructor
   ↓
2. onAttachedToWindow()
   ↓
3. onMeasure() — может вызваться много раз
   ↓
4. onSizeChanged() — первый раз после onLayout
   ↓
5. onLayout() — может вызваться много раз
   ↓
6. onDraw() — вызывается постоянно
   ↓
7. [User interactions, animations]
   ↓
8. onDetachedFromWindow() — конец

Практический пример: Custom ProgressBar

class CustomProgressBar @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {
    
    private var progress = 0f
    private var maxProgress = 100f
    private val barPaint = Paint().apply {
        color = Color.BLUE
        strokeWidth = 10f
    }
    private val backgroundPaint = Paint().apply {
        color = Color.GRAY
        strokeWidth = 10f
    }
    
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        
        val barHeight = 20f
        val barTop = (height - barHeight) / 2
        
        // Фон прогресс-бара
        canvas.drawRect(
            0f, barTop,
            width.toFloat(), barTop + barHeight,
            backgroundPaint
        )
        
        // Заполненная часть
        val filledWidth = (width * (progress / maxProgress))
        canvas.drawRect(
            0f, barTop,
            filledWidth, barTop + barHeight,
            barPaint
        )
    }
    
    fun setProgress(newProgress: Float) {
        progress = newProgress.coerceIn(0f, maxProgress)
        invalidate()  // Перерисовать
    }
    
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        // Например, запустить загрузку данных
        simulateProgress()
    }
    
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        // Отменить таймер прогресса
    }
}

Важные выводы

  1. Constructor → инициализация
  2. onAttachedToWindow() → регистрировать слушатели, стартовать анимации
  3. onMeasure() → вычислить размер, оптимизировать
  4. onLayout() → разместить дочерние View
  5. onDraw() → рисовать, не создавать объекты
  6. onDetachedFromWindow() → очистить ресурсы, отписаться от слушателей

Основное правило: оптимизируй onDraw(), очищай в onDetachedFromWindow()

Какие знаешь методы жизненного цикла View? | PrepBro