0Pricing
Android Academy · Lección

Vistas personalizadas

Dibuje componentes de interfaz personalizados desde cero. Extienda View, implemente onMeasure y onDraw con Canvas y Paint, defina atributos XML personalizados y cree vistas compuestas.

Vistas personalizadas es una lección gratuita de Android Academy en CoddyKit. Esta es la lección 3 de 5. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Android Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Android Academy incluye 5 lecciones en total.

¿Por qué utilizar vistas personalizadas?

A veces, las vistas integradas de Android no hacen exactamente lo que necesita. Las vistas personalizadas le permiten:

  • Dibujar formas, gráficos, anillos de progreso o diagramas que no existen en el SDK
  • Crear componentes de interfaz de marca que se utilicen de forma coherente en toda la aplicación
  • Combinar varias vistas en un único componente reutilizable (Compound View)
  • Mejorar el rendimiento eliminando jerarquías de layouts anidadas

Extender View

Cree una vista personalizada extendiendo View (o una subclase como ImageView). Sobrescriba el constructor requerido:

class CircleProgressView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 100f)
            invalidate()   // trigger redraw
        }

    // onMeasure and onDraw go here
}

onMeasure()

onMeasure() indica al elemento padre qué tamaño desea tener esta vista. Llame siempre a setMeasuredDimension() al final:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val desiredSize = 200  // dp → pixels
    val px = (desiredSize * resources.displayMetrics.density).toInt()

    val width  = resolveSize(px, widthMeasureSpec)
    val height = resolveSize(px, heightMeasureSpec)

    // Square: same width and height
    val size = minOf(width, height)
    setMeasuredDimension(size, size)
}

Paint — la herramienta de dibujo

Un objeto Paint contiene la información de estilo para dibujar (color, ancho del trazo y antialiasing). Créelo una sola vez; nunca dentro de onDraw():

class CircleProgressView ... : View(...) {

    // Create Paint in init, not in onDraw!
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#6750A4")
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }
}

onDraw() — dibujar la vista

onDraw(canvas) se llama cada vez que es necesario volver a dibujar la vista. Utilice Canvas para dibujar formas, texto y mapas de bits:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    val cx = width / 2f
    val cy = height / 2f
    val radius = (minOf(width, height) / 2f) - 30f

    // Draw background circle
    canvas.drawCircle(cx, cy, radius, backgroundPaint)

    // Draw progress arc
    val oval = RectF(cx - radius, cy - radius, cx + radius, cy + radius)
    val sweepAngle = 360f * (progress / 100f)
    canvas.drawArc(oval, -90f, sweepAngle, false, progressPaint)
}

invalidate() frente a requestLayout()

Hay dos métodos para activar una actualización:

  • invalidate() — vuelve a dibujar la vista (llama a onDraw). Úselo cuando cambie el contenido, pero el tamaño permanezca igual.
  • requestLayout() — vuelve a medir y a dibujar (llama primero a onMeasure y después a onDraw). Úselo cuando cambie el tamaño de la vista.

Atributos personalizados (attrs.xml)

Declare atributos XML personalizados para poder configurar la vista desde los archivos de layout:

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CircleProgressView">
        <attr name="progressColor" format="color" />
        <attr name="trackColor"    format="color" />
        <attr name="strokeWidth"   format="dimension" />
        <attr name="progress"      format="float" />
    </declare-styleable>
</resources>

Leer atributos personalizados

Lea los atributos en el constructor mediante obtainStyledAttributes:

init {
    context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView).use { ta ->
        progressPaint.color = ta.getColor(
            R.styleable.CircleProgressView_progressColor,
            Color.parseColor("#6750A4")
        )
        backgroundPaint.color = ta.getColor(
            R.styleable.CircleProgressView_trackColor,
            Color.LTGRAY
        )
        val sw = ta.getDimension(R.styleable.CircleProgressView_strokeWidth, 20f)
        progressPaint.strokeWidth = sw
        backgroundPaint.strokeWidth = sw
        progress = ta.getFloat(R.styleable.CircleProgressView_progress, 0f)
    }
}

Usar la vista personalizada en XML

Utilice su vista personalizada en el XML del layout mediante la ruta completa de la clase y los atributos personalizados:

<!-- In any layout XML -->
<com.example.myapp.CircleProgressView
    android:id="@+id/progressView"
    android:layout_width="120dp"
    android:layout_height="120dp"
    app:progress="65"
    app:progressColor="@color/primary"
    app:trackColor="@color/surface_variant"
    app:strokeWidth="12dp" />

<!-- Control in code: -->
binding.progressView.progress = 75f

Compound Views

Una compound view combina vistas existentes en un único componente reutilizable. Extienda una clase de layout como LinearLayout:

class SearchBarView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {

    private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)

    init {
        orientation = HORIZONTAL
        binding.ivSearch.setOnClickListener {
            onSearchClick?.invoke(binding.etSearch.text.toString())
        }
    }

    var onSearchClick: ((String) -> Unit)? = null

    fun setHint(hint: String) { binding.etSearch.hint = hint }
}

Dibujar texto

Dibuje texto en un Canvas con drawText(). Céntrelo con precisión mediante Paint.getFontMetrics():

private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.BLACK
    textSize = 48f
    textAlign = Paint.Align.CENTER
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val cx = width / 2f
    val cy = height / 2f

    // Vertically center the text:
    val fm = textPaint.fontMetrics
    val textY = cy - (fm.ascent + fm.descent) / 2f
    canvas.drawText("${progress.toInt()}%", cx, textY, textPaint)
}

Comprobación rápida

¿Qué método debe llamar para volver a dibujar su vista personalizada cuando cambian los datos, pero el tamaño permanece igual?

Resumen: vistas personalizadas

Ahora puede dibujar cualquier cosa en pantalla:

  • Extienda View con @JvmOverloads constructor
  • onMeasure() — indique el tamaño deseado con setMeasuredDimension()
  • Paint — define el estilo; créelo una sola vez, no dentro de onDraw()
  • onDraw(canvas) — dibuje círculos, arcos, texto y mapas de bits
  • Use invalidate() para volver a dibujar y requestLayout() para volver a medir
  • Atributos personalizados mediante attrs.xml para la configuración desde XML
  • Compound view — extienda un layout para combinar vistas existentes

Siguiente: dé vida a su interfaz con animaciones y transiciones.

Preguntas frecuentes

¿La lección «Vistas personalizadas» es gratis?

Sí — el texto completo de «Vistas personalizadas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Android Academy, actualiza a CoddyKit PRO. El curso de Android Academy incluye 5 lecciones en total.

¿Qué aprenderé en «Vistas personalizadas»?

Dibuje componentes de interfaz personalizados desde cero. Extienda View, implemente onMeasure y onDraw con Canvas y Paint, defina atributos XML personalizados y cree vistas compuestas. Practicas Android Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Android Academy?

No se requiere experiencia previa. Android Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 5.

¿Cuánto tiempo toma la lección «Vistas personalizadas»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Android Academy?

Sí. Cada lección de Android Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Fundamentos de Material Design
  2. Temas y estilos
  3. Vistas personalizadas
  4. Animaciones y transiciones
  5. Navegación inferior y pestañas
← Volver a Android Academy