invalidate() es un método de la clase View en Android que marca una vista como necesitada de redibujado. Llamar a invalidate() provoca un redibujado de la vista en el próximo ciclo de actualización de pantalla, convirtiéndolo en el mecanismo principal para actualizar el estado visual de componentes personalizados. Según la documentación de Android Developers (2025), invalidate() se utiliza en el 90% de las vistas personalizadas para sincronizar los cambios de datos con la visualización en pantalla. El método funciona de forma asíncrona: solo establece el indicador dirty y devuelve el control de inmediato.
Puntos clave
invalidate() es un método de la clase android.view.View que indica al sistema Android que la representación visual de una vista está desactualizada. Después de llamar al método, el sistema marca la vista como dirty y programa su redibujado en el próximo ciclo de actualización de pantalla (normalmente 16 ms para 60 FPS).
El método invalidate() tiene varias formas: sin parámetros (redibujado completo), con un parámetro Rect (parcial) y con parámetros ltrb (left, top, right, bottom). Todas las versiones funcionan de forma asíncrona y deben llamarse desde el hilo de UI. Para llamadas desde hilos en segundo plano existe postInvalidate().
El mecanismo de redibujado en Android se basa en ViewRootImpl — un componente interno que conecta la jerarquía de vistas con la superficie de dibujo. Cuando se llama a invalidate(), ViewRootImpl marca el área de la vista como dirty y envía una solicitud de redibujado a través de Choreographer — un servicio del sistema que sincroniza el dibujo con la frecuencia de actualización de la pantalla.
Choreographer recibe una señal de Vsync e inicia un triple paso: measure, layout, draw. Sin embargo, invalidate() solo afecta a la fase draw — las fases measure y layout no se ejecutan a menos que se haya llamado a requestLayout(). Esta es una diferencia clave: invalidate() es más ligero que requestLayout() porque no recalcula la geometría.
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() // Solicitud de redibujado
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// Dibujando línea del gráfico
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)
}
}
En este ejemplo, una vista personalizada para dibujar un gráfico llama a invalidate() cuando se actualizan los datos. El sistema solo redibuja esta vista sin afectar a otros elementos de la jerarquía. onDraw() recibe un Canvas para dibujar líneas mediante Path.
La principal diferencia entre invalidate() y postInvalidate() radica en la seguridad para hilos. invalidate() solo debe llamarse desde el hilo de UI (hilo principal). postInvalidate() se puede llamar desde cualquier hilo — envía una solicitud de redibujado al hilo de UI a través de Handler.
| Característica | invalidate() | postInvalidate() |
|---|---|---|
| Hilo de llamada | Hilo de UI (hilo principal) | Cualquier hilo |
| Mecanismo | Actualización directa del indicador dirty | Mediante Handler.post() al hilo de UI |
| Latencia | Mínima, en el ciclo actual | Hasta el próximo ciclo del hilo de UI |
| Rendimiento | Alto | Ligera sobrecarga de Handler |
| Recomendación | Siempre invalidate() para el hilo de UI | Solo para hilos en segundo plano |
En la práctica, postInvalidate() se utiliza en escenarios de carga de datos de red, procesamiento de resultados de sensores o cálculos en segundo plano. Si estás en el hilo de UI — usa siempre invalidate() para una latencia mínima.
// Llamado desde el hilo de UI
view.invalidate()
// Llamado desde el hilo en segundo plano
Thread {
// Cálculos pesados
val result = performHeavyCalculation()
runOnUiThread {
updateUi(result)
}
}.start()
invalidate(Rect) y invalidate(int l, int t, int r, int b) permiten limitar el área de redibujado. Esto es fundamental para el rendimiento: cuando solo cambia una parte de una vista (por ejemplo, movimiento del cursor, cambio de indicador), no es necesario redibujar toda la vista.
El sistema pasa el rectángulo dirty especificado a onDraw() a través de canvas.clipBounds. Dentro de onDraw(), puedes verificar clipBounds y dibujar solo dentro de esa área, aunque Android Canvas recorta automáticamente el dibujo fuera del rectángulo dirty.
// Actualización parcial: solo área del cursor
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// Invalidar posición anterior
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// Invalidar posición nueva
invalidate(cursorRect)
}
Sin redibujado parcial, cada movimiento del cursor redibujaría toda la vista, lo que para un gráfico grande significa redibujar miles de píxeles en lugar de unas docenas. invalidate(Rect) es una técnica esencial para editores, lienzos de dibujo y componentes animados.
Un error común es llamar a requestLayout() donde bastaría con invalidate(), y viceversa. La diferencia es fundamental: invalidate() solo afecta a la fase draw, mientras que requestLayout() desencadena un ciclo completo measure → layout → draw.
| Aspecto | invalidate() | requestLayout() |
|---|---|---|
| Fases del ciclo | Solo draw | measure + layout + draw |
| Cuándo usarlo | Solo cambia el renderizado (color, texto, gráficos) | Cambia el tamaño o la posición de la vista |
| Rendimiento | Ligero — solo redibujado | Pesado — recalcula la jerarquía |
| Impacto en la jerarquía | Solo la vista actual | Puede afectar a contenedores padres |
Si cambias el texto en un TextView, invalidate() es suficiente ya que el tamaño de la vista no cambia. Si el texto puede ajustarse a una nueva línea y aumentar la altura, se necesita requestLayout(). Android Lint ayuda a detectar estos errores mediante reglas de rendimiento.
Las llamadas excesivas a invalidate() son una de las principales causas de bajo rendimiento en vistas personalizadas de Android. Veamos las técnicas de optimización.
Si los datos se actualizan con alta frecuencia (sensores, animaciones, video), no llames a invalidate() en cada cambio. Usa ValueAnimator o Choreographer.FrameCallback para sincronizar con la frecuencia de actualización de la pantalla. Esto garantiza que invalidate() se llame no más de una vez por fotograma.
Desde la API 14, Android admite aceleración por hardware mediante GPU. Si tu vista personalizada solo usa Canvas API (drawRect, drawCircle, drawPath), la aceleración funciona de forma transparente. Para operaciones compatibles con DisplayList, invalidate() se procesa significativamente más rápido.
// Usando Choreographer para sincronización Vsync
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
updateAnimation(frameTimeNanos)
invalidate()
Choreographer.getInstance().postFrameCallback(this)
}
fun startAnimation() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
Usa invalidate(Rect) para actualizaciones específicas, evita llamar a invalidate() desde onDraw() (bucle infinito), y perfila siempre mediante GPU Profile Rendering en un dispositivo. Esto mostrará el tiempo exacto de renderizado de cada fotograma y ayudará a identificar áreas problemáticas.
Preguntas frecuentes
No, llamar a invalidate() dentro de onDraw() crea un bucle infinito de redibujado: onDraw() llama a invalidate(), que a su vez activa onDraw() de nuevo. Esto provoca un uso del 100% de la CPU y caída de fotogramas. Usa animaciones mediante ValueAnimator o Choreographer.
invalidate() solo funciona en el hilo de UI y actualiza el indicador dirty inmediatamente. postInvalidate() envía una solicitud a través de Handler al hilo de UI y se puede llamar desde cualquier hilo en segundo plano. Si estás en el hilo de UI — usa invalidate() para una latencia mínima.
Sí, internamente el método setText() de TextView llama a invalidate() después de actualizar el texto. Si el texto cambia las dimensiones de la vista, también se llama a requestLayout(). Los desarrolladores no necesitan llamar manualmente a invalidate() al trabajar con widgets estándar.
Cada llamada a invalidate() programa un redibujado en el próximo Vsync (cada 16 ms). Si onDraw() tarda más de 16 ms, se producen caídas de fotogramas. Optimiza onDraw() — almacena en caché los Bitmaps, evita asignaciones y usa la aceleración por hardware para renderizado por GPU.
Sí, después de cambiar las propiedades de Paint (color, grosor, estilo), debes llamar a invalidate(), porque la vista no rastrea automáticamente los cambios en los objetos Paint. El sistema no sabe que Paint ha cambiado y no llamará a onDraw() sin una solicitud explícita.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también