Какие знаешь методы жизненного цикла View?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл 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()
// Отменить таймер прогресса
}
}
Важные выводы
- Constructor → инициализация
- onAttachedToWindow() → регистрировать слушатели, стартовать анимации
- onMeasure() → вычислить размер, оптимизировать
- onLayout() → разместить дочерние View
- onDraw() → рисовать, не создавать объекты
- onDetachedFromWindow() → очистить ресурсы, отписаться от слушателей
Основное правило: оптимизируй onDraw(), очищай в onDetachedFromWindow()