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() // Запит перемалювання
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// Малювання лінії графіка
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() для мінімальної затримки.
// Викликано з UI потоку
view.invalidate()
// Викликано з фонового потоку
Thread {
// Важкі обчислення
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-прямокутника.
// Часткове оновлення: лише область курсора
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// Інвалідувати стару позицію
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// Інвалідувати нову позицію
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() обробляється значно швидше.
// Використання Choreographer для синхронізації Vsync
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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також