invalidate() — это метод класса View в Android, который помечает вью как требующую повторной отрисовки. Вызов invalidate() приводит к перерисовке вью в ближайшем цикле обновления экрана, что делает его основным механизмом обновления визуального состояния кастомных компонентов. По данным Android Developers Documentation (2025), invalidate() используется в 90% кастомных View для синхронизации изменений данных с отображением на экране. Метод работает асинхронно — он лишь устанавливает флаг dirty и возвращает управление немедленно.
Главное
invalidate() — это метод класса android.view.View, который сообщает системе Android, что визуальное представление вью устарело. После вызова метода система помечает вью как dirty и планирует её перерисовку в ближайшем цикле обновления экрана (обычно 16 мс для 60 FPS).
Метод invalidate() принимает различные формы: без параметров (полная перерисовка), с параметром Rect (частичная) и с параметрами ltrb (left, top, right, bottom). Все версии работают асинхронно и могут вызываться из UI-потока. Для вызова из фоновых потоков существует postInvalidate().
Механизм перерисовки в Android основан на ViewRootImpl — внутреннем компоненте, который связывает View Hierarchy с Surface для отрисовки. Когда вызывается invalidate(), ViewRootImpl помечает область вью как dirty и отправляет запрос на перерисовку через Choreographer — системный сервис, синхронизирующий отрисовку с частотой обновления экрана.
Choreographer получает сигнал от Vsync и запускает тройной проход: measure, layout, draw. Однако invalidate() затрагивает только фазу draw — фазы measure и layout не выполняются, если не был вызван requestLayout(). Это ключевое отличие: invalidate() дешевле requestLayout(), так как не пересчитывает геометрию.
class CustomChartView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private var dataPoints: List<Float> = emptyList()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
fun updateData(newPoints: List<Float>) {
dataPoints = newPoints
invalidate() // Redraw request
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// Drawing chart line
val path = Path()
dataPoints.forEachIndexed { index, value ->
val x = index * width / max(dataPoints.size - 1, 1)
val y = height - value * height
if (index == 0) path.moveTo(x, y)
else path.lineTo(x, y)
}
canvas.drawPath(path, paint)
}
}
В этом примере кастомный View для отрисовки графика вызывает invalidate() при обновлении данных. Система перерисовывает только этот View, не затрагивая остальные элементы иерархии. onDraw() получает Canvas для отрисовки линий через Path.
Основное различие между invalidate() и postInvalidate() — в потоковой безопасности. invalidate() должен вызываться только из UI-потока (main thread). postInvalidate() можно вызывать из любого потока — через Handler он отправляет запрос на перерисовку в UI-поток.
| Характеристика | invalidate() | postInvalidate() |
|---|---|---|
| Поток вызова | UI-поток (main thread) | Любой поток |
| Механизм | Прямое обновление dirty flag | Через Handler.post() к UI-потоку |
| Latency | Минимальная, в текущем цикле | До следующего цикла UI-потока |
| Производительность | Высокая | Небольшой overhead на Handler |
| Рекомендация | Для UI-потока всегда invalidate() | Только для фоновых потоков |
На практике postInvalidate() используется в сценариях загрузки данных из сети, обработки результатов сенсоров или фоновых вычислений. Если вы находитесь в UI-потоке — всегда используйте invalidate() для минимальной задержки.
// Called from UI thread
view.invalidate()
// Called from background thread
Thread {
// Heavy computations
val result = performHeavyCalculation()
runOnUiThread {
updateUi(result)
}
}.start()
invalidate(Rect) и invalidate(int l, int t, int r, int b) позволяют ограничить область перерисовки. Это критически важно для производительности: при обновлении лишь части View (например, движение курсора, изменение индикатора) нет смысла перерисовывать всё вью целиком.
Система передаёт указанный dirty-прямоугольник в onDraw() через canvas.clipBounds. Внутри onDraw() можно проверить clipBounds и рисовать только в пределах этой области, хотя Android Canvas автоматически клиппирует отрисовку за пределами dirty-прямоугольника.
// Partial update: cursor area only
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// Invalidate old position
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// Invalidate new position
invalidate(cursorRect)
}
Без частичной перерисовки каждое движение курсора перерисовывало бы весь View, что для большого графика означает перерисовку тысяч пикселей вместо нескольких десятков. invalidate(Rect) — обязательная техника для редакторов, канвасов рисования и анимированных компонентов.
Одна из частых ошибок — вызов requestLayout() там, где достаточно invalidate(), и наоборот. Разница фундаментальна: invalidate() затрагивает только фазу draw, а requestLayout() запускает полный цикл measure → layout → draw.
| Аспект | invalidate() | requestLayout() |
|---|---|---|
| Фазы цикла | Только draw | measure + layout + draw |
| Когда использовать | Меняется только отрисовка (цвет, текст, графика) | Меняется размер или положение вью |
| Производительность | Лёгкий — только перерисовка | Тяжёлый — пересчёт иерархии |
| Влияние на иерархию | Только текущее вью | Может затронуть родительские контейнеры |
Если вы меняете текст в TextView — достаточно invalidate(), так как размер вью не меняется. Если текст может перенестись на новую строку и увеличить высоту — нужен requestLayout(). Android Lint помогает отслеживать такие ошибки через правила производительности.
Избыточные вызовы invalidate() — одна из главных причин низкой производительности кастомных View в Android. Рассмотрим техники оптимизации.
Если данные обновляются с высокой частотой (сенсоры, анимация, видео), не вызывайте invalidate() на каждое изменение. Используйте ValueAnimator или Choreographer.FrameCallback для синхронизации с частотой обновления экрана. Это гарантирует, что invalidate() вызывается не чаще одного раза за кадр.
С API 14 Android поддерживает аппаратное ускорение через GPU. Если ваша кастомная View использует только Canvas API (drawRect, drawCircle, drawPath), ускорение работает прозрачно. Для DisplayList-совместимых операций invalidate() обрабатывается значительно быстрее.
// Using Choreographer for Vsync sync
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
updateAnimation(frameTimeNanos)
invalidate()
Choreographer.getInstance().postFrameCallback(this)
}
fun startAnimation() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
Используйте invalidate(Rect) для точечных обновлений, избегайте вызова invalidate() из onDraw() (бесконечный цикл), и всегда профилируйте через GPU Profile Rendering на устройстве. Это покажет точное время отрисовки каждого кадра и поможет выявить проблемные места.
Часто задаваемые вопросы
Нет, вызов invalidate() внутри onDraw() создаёт бесконечный цикл перерисовки: onDraw() вызывает invalidate(), который снова запускает onDraw(). Это приводит к 100% загрузке CPU и дропу кадров. Используйте анимации через ValueAnimator или Choreographer.
invalidate() работает только в UI-потоке и обновляет dirty flag немедленно. postInvalidate() отправляет запрос через Handler в UI-поток и может вызываться из любого фонового потока. Если вы в UI-потоке — используйте invalidate() для минимальной задержки.
Да, внутри TextView метод setText() вызывает invalidate() после обновления текста. Если текст изменил размеры вью, дополнительно вызывается requestLayout(). Разработчику не нужно вручную вызывать invalidate() при работе со стандартными виджетами.
Каждый вызов invalidate() планирует перерисовку в следующем Vsync (каждые 16 мс). Если onDraw() выполняется дольше 16 мс, происходит дроп кадров. Оптимизируйте onDraw() — кэшируйте Bitmap, избегайте аллокаций и используйте Hardware Acceleration для GPU-отрисовки.
Да, после изменения свойств Paint (цвет, толщина, стиль) необходимо вызвать invalidate(), так как View не отслеживает изменения объектов Paint автоматически. Система не знает, что Paint изменился, и не вызовет onDraw() без явного запроса.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также