Custom View dans Android — essence, dessin et gestion d’événements

Auteur : IT Sectr Publié le : 2026-07-20 Temps de lecture : 9 min

Custom View est un composant d’interface personnalisé dans Android, créé en héritant de la classe View ou de ses sous-classes (Button, TextView, ImageView) et en remplaçant les méthodes clés : onDraw pour le dessin, onMeasure pour mesurer les tailles et onTouchEvent pour gérer les touches. Selon le guide du développeur Android de Google (2024), Custom View est utilisé lorsque les composants standard du SDK Android ne fournissent pas le comportement ou l’apparence requis — par exemple, pour les animations personnalisées, les formes non standard, les graphiques spécialisés et les éléments de jeu.

Points clés

  • Custom View — créé en héritant de View ou de ses sous-classes avec remplacement de onDraw, onMeasure, onTouchEvent.
  • Le dessin est effectué via l’API Canvas — formes géométriques, texte, chemins, Bitmap et animation.
  • onMeasure gère les tailles correctes de la View en tenant compte des paramètres de layout et du padding.
  • Les attributs XML personnalisés sont déclarés dans res/values/attrs.xml et lus dans le constructeur de la View.
  • La gestion des touches via onTouchEvent permet d’ajouter la prise en charge des gestes, du glisser-déposer et du multi-tactile.

Qu’est-ce que Custom View dans Android ?

Custom View est une classe qui hérite de android.view.View (ou de l’une de ses sous-classes) et remplace les méthodes système pour implémenter une logique personnalisée de dessin, de mesure et de gestion des événements. Custom View est un mécanisme fondamental d’Android pour créer des composants d’interface uniques.

Android propose deux approches pour créer un Custom View : hériter de View (dessin entièrement personnalisé via onDraw) et hériter d’une sous-classe View existante (par exemple, remplacer Button ou TextView pour ajouter des fonctionnalités tout en conservant le comportement de base).

Constructeur de base d’un Custom View doit accepter Context et AttributeSet — cela permet au système de créer la View à partir du balisage XML. Si la View est utilisée uniquement depuis le code, un constructeur avec Context est suffisant. Un troisième constructeur avec style est nécessaire pour la prise en charge des thèmes Android.

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

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        strokeWidth = 4f
        style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawCircle(width / 2f,
            height / 2f,
            50f, paint)
    }
}

Custom View minimal se compose d’un constructeur et de onDraw. Cependant, pour fonctionner correctement dans le système de layout Android, onMeasure doit également être remplacé — sinon la View peut s’afficher avec des tailles nulles ou réagir incorrectement aux paramètres de layout wrap_content.

Selon Google I/O 2023, les Custom Views sont utilisés dans 65 % des applications du top 100 du Google Play. Les raisons les plus courantes de créer un Custom View : animations personnalisées (35 %), graphiques et diagrammes personnalisés (25 %), éléments de contrôle spécialisés (20 %) et composants de marque (20 %).

onMeasure et onLayout : gestion des tailles

onMeasure est la méthode où la View indique ses dimensions souhaitées en fonction du MeasureSpec reçu (contraintes du parent). Sans une implémentation correcte de onMeasure, la View peut avoir une hauteur nulle avec wrap_content ou occuper toute la surface de l’écran avec match_parent.

MeasureSpec se compose d’un mode (EXACTLY, AT_MOST, UNSPECIFIED) et d’une valeur. EXACTLY — le parent a défini une taille exacte (match_parent ou valeur fixe). AT_MOST — le parent a défini un maximum (wrap_content). UNSPECIFIED — aucune restriction (ScrollView, ListView).

kotlin
override fun onMeasure(
    widthMeasureSpec: Int,
    heightMeasureSpec: Int
) {
    val desiredWidth = paddingLeft + paddingRight
        + DEFAULT_WIDTH
    val desiredHeight = paddingTop + paddingBottom
        + DEFAULT_HEIGHT

    val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
    val modeWidth = MeasureSpec.getMode(widthMeasureSpec)

    val resultWidth = when (modeWidth) {
        MeasureSpec.EXACTLY -> measuredWidth
        MeasureSpec.AT_MOST ->
            desiredWidth.coerceAtMost(measuredWidth)
        else -> desiredWidth
    }

    val resultHeight = resolveSize(desiredHeight,
        heightMeasureSpec)

    setMeasuredDimension(resultWidth, resultHeight)
}

resolveSize est une méthode utilitaire d’Android qui simplifie l’implémentation de onMeasure. Elle prend la taille souhaitée et MeasureSpec et retourne la valeur correcte : pour EXACTLY — la taille exacte, pour AT_MOST — le minimum entre la souhaitée et la maximale, pour UNSPECIFIED — la taille souhaitée.

onLayout pour View (pas ViewGroup) n’est généralement pas remplacé — il est appelé par le parent pour positionner les éléments enfants. Pour ViewGroup, onLayout est obligatoire — vous devez appeler layout() pour chaque View enfant.

onDraw et API Canvas : dessin

onDraw est le cœur du Custom View. Cette méthode est appelée par Android à chaque redessin de la View. Un objet Canvas lui est transmis, à travers lequel tout le dessin est effectué : formes géométriques, texte, chemins, Bitmap et animations.

API Canvas fournit des méthodes pour toutes les opérations de base : drawCircle, drawRect, drawLine, drawPath, drawText, drawBitmap, drawArc, drawOval. Pour configurer le style, Paint est utilisé — un objet qui définit la couleur, l’épaisseur, le style de remplissage, la police, les ombres et les effets.

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

    // Arrière-plan
    canvas.drawRect(0f, 0f,
        width.toFloat(), height.toFloat(), bgPaint)

    // Texte
    canvas.drawText("Custom View",
        paddingLeft.toFloat(),
        paddingTop.toFloat() + textPaint.textSize,
        textPaint)

    // Cercle
    canvas.drawCircle(
        width / 2f,
        height / 2f,
        radius, circlePaint)

    // Chemin (courbe de Bezier)
    val path = Path().apply {
        moveTo(0f, height.toFloat())
        quadTo(width / 2f, 0f,
            width.toFloat(), height.toFloat())
    }
    canvas.drawPath(path, pathPaint)
}

Paint est le configurateur de style de dessin. L’anticrénelage (Paint.ANTI_ALIAS_FLAG) est obligatoire pour des bords lisses. Le texte subpixel (SUBPIXEL_TEXT_FLAG) améliore la qualité du texte. Style.FILL, STROKE et FILL_AND_STROKE déterminent si la forme sera remplie, contournée ou les deux.

Important : ne créez pas d’objets Paint, Path, Rect dans onDraw — cela entraîne des allocations dans la boucle de dessin et déclenche le garbage collection, ce qui réduit les FPS. Créez tous les objets de dessin dans le constructeur ou dans la méthode init. Pour l’animation, utilisez ValueAnimator ou ObjectAnimator avec invalidate() pour déclencher le redessin.

Attributs XML personnalisés dans attrs.xml

Attributs personnalisés permettent de configurer un Custom View à partir du balisage XML, tout comme les attributs standard android:layout_width ou android:background. Les attributs sont déclarés dans le fichier res/values/attrs.xml avec un nom, un type et une valeur par défaut optionnelle.

Types d’attributs : string, integer, float, boolean, color, dimension, enum, flag, fraction, reference (référence de ressource). Pour chaque type, Android analyse automatiquement la valeur du XML et la transmet à TypedArray.

xml
<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="circleColor"
            format="color" />
        <attr name="circleRadius"
            format="dimension" />
        <attr name="labelText"
            format="string" />
        <attr name="showAnimation"
            format="booléen" />
    </declare-styleable>
</resources>

Dans le constructeur de Custom View, les attributs sont lus via context.obtainStyledAttributes, qui retourne un TypedArray. TypedArray fournit des méthodes d’accès typées : getColor, getDimension, getString, getBoolean, getInt. Après la lecture, recycle() doit être appelé sur le TypedArray pour libérer les ressources.

kotlin
init {
    val typedArray = context.obtainStyledAttributes(
        attrs,
        R.styleable.CustomView
    )

    circleColor = typedArray.getColor(
        R.styleable.CustomView_circleColor,
        Color.BLUE
    )
    circleRadius = typedArray.getDimension(
        R.styleable.CustomView_circleRadius,
        50f
    )
    labelText = typedArray.getString(
        R.styleable.CustomView_labelText
    ) ?: "Default"

    typedArray.recycle()
}

Utilisation en XML : ajoutez l’espace de noms app (xmlns:app="http://schemas.android.com/apk/res-auto") et utilisez des attributs personnalisés comme app:circleColor="@color/red". Android Studio autocomplétera et validera les types d’attributs si la déclaration dans attrs.xml est correcte.

Gestion des touches avec onTouchEvent

onTouchEvent est la méthode appelée à chaque touche sur la View. Elle reçoit un objet MotionEvent avec des informations sur le type d’événement (ACTION_DOWN, ACTION_MOVE, ACTION_UP, ACTION_CANCEL), les coordonnées, la pression et le nombre de doigts (multi-tactile).

Pour gérer les gestes complexes (balayage, pincement, appui long), utilisez GestureDetector ou ScaleGestureDetector en combinaison avec onTouchEvent. GestureDetector simplifie la reconnaissance de onSingleTapUp, onFling, onLongPress, onDoubleTap et d’autres gestes standard.

kotlin
private val gestureDetector = GestureDetector(
    context, object : GestureDetector.SimpleOnGestureListener() {
    override fun onSingleTapUp(e: MotionEvent): Boolean {
        handleTap(e.x, e.y)
        return true
    }
})

override fun onTouchEvent(event: MotionEvent): Boolean {
    val handled = gestureDetector.onTouchEvent(event)

    when (event.action) {
        MotionEvent.ACTION_MOVE -> {
            currentX = event.x
            currentY = event.y
            invalidate()
            return true
        }
    }

    return handled || super.onTouchEvent(event)
}

Test de collision — déterminer si une touche a atteint une zone spécifique de la View. Pour les zones rectangulaires, utilisez Rect.contains(x, y). Pour les cercles — vérification de la distance depuis le centre : sqrt(dx² + dy²) < radius. Pour les formes arbitraires — Path.op() ou Region.contains().

Multi-tactile est géré via MotionEvent.getPointerCount() et getPointerId(index). Chaque doigt reçoit un ID unique qui est conservé de ACTION_DOWN à ACTION_POINTER_UP. ACTION_MOVE peut contenir des données de tous les doigts actifs — utilisez getHistoricalX/Y pour l’interpolation du mouvement.

Optimisation des performances de Custom View

Custom View peut devenir un goulot d’étranglement de performances si onDraw est appelé fréquemment (animation, défilement) ou contient des opérations lourdes. Android fournit plusieurs mécanismes pour optimiser le rendu des composants personnalisés.

Type de couche pour le dessin complexe

View.setLayerType permet de basculer le rendu de la View vers une couche logicielle (LAYER_TYPE_SOFTWARE) ou matérielle (LAYER_TYPE_HARDWARE). La couche logicielle est utile pour les graphiques Canvas complexes qui ne sont pas pris en charge par l’accélération matérielle — par exemple, drawTextOnPath ou les effets Path complexes.

Invalider uniquement la zone nécessaire

invalidate(Rect) redessine uniquement la zone spécifiée de la View, pas le composant entier. Ceci est crucial pour les grands Custom Views (graphiques, cartes, canevas de dessin), où le redessin complet de chaque image provoque des chutes de FPS. Utilisez postInvalidateOnAnimation() pour la synchronisation avec vsync.

kotlin
class EfficientCustomView
    @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {

    // Objets créés une fois dans le constructeur
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rect = Rect()
    private val position = PointF()

    fun updatePosition(x: Float, y: Float) {
        position.set(x, y)
        // Redessiner uniquement la zone autour du point
        rect.set(
            (x - 10).toInt(),
            (y - 10).toInt(),
            (x + 10).toInt(),
            (y + 10).toInt()
        )
        invalidate(rect)
    }
}

Accélération matérielle est activée par défaut sur Android 3.0+ (API 11+). Les opérations Canvas drawCircle, drawRect, drawBitmap sont accélérées par matériel et exécutées sur le GPU. Cependant, drawTextOnPath, drawVertices, les opérations de clipping complexes ne sont pas accélérées — utilisez LAYER_TYPE_SOFTWARE pour elles.

Selon Android Performance Patterns (Google, 2023), les principales causes de lenteur de Custom View — allocations dans onDraw (création d’objets à chaque image), redessin de toute la View lors du changement d’un élément et absence de LayerType.HARDWARE pour le contenu statique. Corriger ces trois problèmes augmente les FPS de 30 à 60 dans la plupart des scénarios.

MéthodeObjectifFréquence d’appel
onDrawDessiner le contenu de la ViewÀ chaque invalidate()
onMeasureDéterminer les tailles de la ViewLors du changement de layout
onLayoutPositionner les Views enfantsAprès onMeasure
onTouchEventGestion des touchesÀ chaque touche
onSizeChangedRéaction au changement de tailleAu premier layout et changement

Questions fréquentes

Quand utiliser Custom View plutôt que Compose ?

Custom View (API Canvas) est justifié lorsque des performances maximales de dessin personnalisé sont nécessaires (graphiques, éditeurs vidéo, cartes), l’intégration avec du code existant basé sur View ou la prise en charge des versions Android inférieures à l’API 21. Jetpack Compose est une approche moderne pour la plupart des nouveaux projets, utilisant une UI déclarative et mieux adaptée aux interfaces dynamiques.

Est-il obligatoire de remplacer onMeasure ?

Oui, si la View utilise wrap_content dans le layout XML. Sans onMeasure, wrap_content se comportera comme match_parent car l’implémentation par défaut de View.onMeasure ne définit pas de taille par défaut. Si la View a toujours une taille fixe ou match_parent, onMeasure peut ne pas être remplacé.

Comment animer un Custom View ?

Utilisez ValueAnimator ou ObjectAnimator pour modifier les propriétés de la View (couleur, rayon, position) et appelez invalidate() dans le callback d’animation pour redessiner. ValueAnimator s’exécute sur le thread principal et est synchronisé avec vsync. Pour les animations complexes (physique, particules), utilisez Choreographer.FrameCallback ou Android Animation Framework.

Comment gérer le multi-tactile dans Custom View ?

Dans onTouchEvent, utilisez MotionEvent.getPointerCount() pour déterminer le nombre de doigts et getPointerId(i) pour suivre chaque doigt par un ID unique. ACTION_POINTER_DOWN / ACTION_POINTER_UP — événements d’ajout/suppression de doigt. Pour le pincement-zoom, utilisez ScaleGestureDetector avec onTouchEvent — il simplifie la détection du pincement.

Peut-on utiliser Custom View dans Jetpack Compose ?

Oui, via AndroidView — une fonction composable qui intègre un élément basé sur View dans la hiérarchie Compose. AndroidView accepte une fabrique pour créer la View et un callback update pour la synchronisation d’état. Cela permet de réutiliser des Custom Views existants dans de nouveaux projets Compose sans réécriture complète.

Résumé

  • Custom View — un composant Android personnalisé créé en héritant de View avec remplacement de onDraw, onMeasure et onTouchEvent pour le dessin et la gestion personnalisés.
  • onDraw et API Canvas — la base du dessin : formes géométriques, texte, Bitmap, chemins — configurés via Paint pour la couleur, le style et la police.
  • onMeasure est important pour le comportement correct de wrap_content ; utilise MeasureSpec (EXACTLY, AT_MOST, UNSPECIFIED) et resolveSize pour calculer les dimensions.
  • Les attributs personnalisés via attrs.xml et TypedArray permettent de configurer Custom View à partir du balisage XML avec la prise en charge de l’autocomplétion dans Android Studio.
  • onTouchEvent avec GestureDetector fournit la gestion des touches, des gestes et du multi-tactile, tandis que le test de collision détermine la zone d’interaction.
  • Performances : créez les objets une fois dans le constructeur, utilisez invalidate(Rect) pour le redessin partiel et LayerType pour les effets complexes.
  • Custom View s’intègre avec Jetpack Compose via AndroidView, permettant de réutiliser des composants prêts à l’emploi dans de nouveaux projets Compose.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi