Rogner des objets Canvas

Cet atelier de programmation fait partie du cours Développement Android avancé en Kotlin. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre, mais ce n'est pas obligatoire. Tous les ateliers de programmation du cours sont listés sur la page de destination des ateliers de programmation Android avancé en Kotlin.

Introduction

Dans cet atelier de programmation, le clipping est une méthode permettant de définir des régions d'une image, d'un canevas ou d'un bitmap qui sont dessinées ou non sur l'écran. L'un des objectifs du clipping est de réduire le surtracé. La superposition se produit lorsqu'un pixel à l'écran est dessiné plusieurs fois pour afficher l'image finale. Lorsque vous réduisez le surtirage, vous minimisez le nombre de fois qu'un pixel ou une région de l'écran est dessiné, afin de maximiser les performances de dessin. Vous pouvez également utiliser le clipping pour créer des effets intéressants dans la conception et l'animation d'interfaces utilisateur.

Par exemple, lorsque vous dessinez une pile de cartes qui se chevauchent comme ci-dessous, au lieu de dessiner entièrement chaque carte de bas en haut, il est généralement plus efficace de ne dessiner que les parties visibles. Le terme "généralement" est utilisé, car les opérations de clipping ont également un coût. De plus, le système Android effectue de nombreuses optimisations de dessin.

Pour ne dessiner que les parties visibles des cartes, vous devez spécifier une région de clipping pour chaque carte. Par exemple, dans le diagramme ci-dessous, lorsqu'un rectangle de masquage est appliqué à une image, seule la partie à l'intérieur de ce rectangle est affichée.

La région de clipping est généralement un rectangle, mais elle peut être n'importe quelle forme ou combinaison de formes, voire du texte. Vous pouvez également spécifier si vous souhaitez inclure ou exclure la région à l'intérieur de la région de clipping. Par exemple, vous pouvez créer une région de clipping circulaire et n'afficher que ce qui se trouve en dehors du cercle.

Dans cet atelier de programmation, vous allez tester différentes méthodes de découpage.

Ce que vous devez déjà savoir

Vous devez maîtriser les éléments suivants :

  • Vous savez créer une application avec un Activity et l'exécuter à l'aide d'Android Studio.
  • Comment créer et dessiner sur un Canvas.
  • Comment créer un View personnalisé et remplacer onDraw() et onSizeChanged().

Points abordés

  • Comment découper des objets pour dessiner sur un Canvas.
  • Découvrez comment enregistrer et restaurer les états de dessin d'un canevas.
  • Appliquer des transformations à un canevas et à du texte

Objectifs de l'atelier

  • Créez une application qui dessine des formes découpées à l'écran, en montrant différentes façons de les découper et leur impact sur leur visibilité.
  • Vous allez également dessiner du texte traduit et incliné.

L'application ClippingExample montre comment utiliser et combiner des formes pour spécifier les parties d'un canevas qui sont affichées dans une vue. La version finale de votre application ressemblera à la capture d'écran ci-dessous.

Vous allez créer cette application de A à Z. Vous devrez donc configurer un projet, définir des dimensions et des chaînes, et déclarer des variables.

Étape 1 : Créer le projet ClippingExample

  1. Créez un projet Kotlin appelé ClippingExample avec le modèle Empty Activity (Activité vide). Utilisez com.example.android comme préfixe du nom du package.
  2. Ouvrez MainActivity.kt.
  3. Dans la méthode onCreate(), remplacez la vue de contenu par défaut et définissez la vue de contenu sur une nouvelle instance de ClippedView. Il s'agira de votre vue personnalisée pour les exemples d'écrêtage que vous allez créer ensuite.
setContentView(ClippedView(this))
  1. Au même niveau que MainActivity.kt, créez un fichier et une classe Kotlin pour une vue personnalisée appelée ClippedView qui étend View. Attribuez-lui la signature ci-dessous. Le reste de votre travail se fera dans ce ClippedView. L'annotation @JvmOverloads indique au compilateur Kotlin de générer des surcharges pour cette fonction qui remplacent les valeurs de paramètre par défaut.
class ClippedView @JvmOverloads constructor(
   context: Context,
   attrs: AttributeSet? = null,
   defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}

Étape 2 : Ajoutez des dimensions et des ressources de chaîne

  1. Définissez les dimensions que vous utiliserez pour les vues rognées dans un nouveau fichier de ressources dans res/values/dimens.xml. Ces dimensions par défaut sont codées en dur et dimensionnées pour s'adapter à un écran assez petit.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">90dp</dimen>
   <dimen name="clipRectBottom">90dp</dimen>
   <dimen name="clipRectTop">0dp</dimen>
   <dimen name="clipRectLeft">0dp</dimen>

   <dimen name="rectInset">8dp</dimen>
   <dimen name="smallRectOffset">40dp</dimen>

   <dimen name="circleRadius">30dp</dimen>
   <dimen name="textOffset">20dp</dimen>
   <dimen name="strokeWidth">4dp</dimen>

   <dimen name="textSize">18sp</dimen>
</resources>

Pour que l'application s'affiche correctement sur un écran plus grand (et pour voir plus facilement les détails), vous pouvez créer un fichier dimens avec des valeurs plus grandes qui ne s'appliquent qu'aux écrans plus grands.

  1. Dans Android Studio, effectuez un clic droit sur le dossier values, puis sélectionnez New > Values resource file (Nouveau > Fichier de ressources de valeurs).
  2. Dans la boîte de dialogue New Resource File (Nouveau fichier de ressources), nommez le fichier dimens. Dans Qualificateurs disponibles, sélectionnez Plus petite largeur d'écran, puis cliquez sur le bouton >> pour l'ajouter à Qualificateurs choisis. Saisissez 480 dans la zone Plus petite largeur d'écran, puis cliquez sur OK.

  1. Le fichier doit s'afficher dans votre dossier de valeurs, comme indiqué ci-dessous.

  1. Si vous ne voyez pas le fichier, passez à la vue Fichiers du projet de l'application. Le chemin d'accès complet du nouveau fichier est indiqué ci-dessous : ClippingExample/app/src/main/res/values-sw480dp/dimens.xml.

  1. Remplacez le contenu par défaut du fichier values-sw480dp/dimens.xml par les dimensions ci-dessous.
<?xml version="1.0" encoding="utf-8"?>
<resources>
   <dimen name="clipRectRight">120dp</dimen>
   <dimen name="clipRectBottom">120dp</dimen>

   <dimen name="rectInset">10dp</dimen>
   <dimen name="smallRectOffset">50dp</dimen>

   <dimen name="circleRadius">40dp</dimen>
   <dimen name="textOffset">25dp</dimen>
   <dimen name="strokeWidth">6dp</dimen>
</resources>
  1. Dans strings.xml, ajoutez les chaînes suivantes. Elles serviront à afficher du texte sur le canevas.
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>

Étape 3 : Créez et initialisez un objet Paint et un objet Path

  1. Revenez à la vue Android de votre projet.
  2. Dans ClippedView, définissez une variable Paint pour dessiner. Activez l'anticrénelage et utilisez l'épaisseur du trait et la taille du texte définies dans les dimensions, comme indiqué ci-dessous.
private val paint = Paint().apply {
   // Smooth out edges of what is drawn without affecting shape.
   isAntiAlias = true
   strokeWidth = resources.getDimension(R.dimen.strokeWidth)
   textSize = resources.getDimension(R.dimen.textSize)
}
  1. Dans ClippedView, créez et initialisez un Path pour stocker localement le chemin de ce qui a été dessiné. Importez android.graphics.Path.
private val path = Path()

Étape 4 : Configurez les formes

Dans cette application, vous affichez plusieurs lignes et deux colonnes de formes découpées de différentes manières.

Elles ont toutes en commun :

  • Grand rectangle (carré) servant de conteneur
  • Une ligne diagonale traversant le grand rectangle
  • Un cercle
  • Une courte chaîne de texte

Dans cette étape, vous configurez les dimensions de ces formes à partir des ressources. Vous n'aurez ainsi à obtenir les dimensions qu'une seule fois lorsque vous les utiliserez plus tard.

  1. Dans ClippedView, sous path, ajoutez des variables pour les dimensions d'un rectangle de clipping autour de l'ensemble des formes.
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
  1. Ajoutez des variables pour l'encart d'un rectangle et le décalage d'un petit rectangle.
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
  1. Ajoutez une variable pour le rayon d'un cercle. Il s'agit du rayon du cercle dessiné à l'intérieur du rectangle.
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
  1. Ajoutez un décalage et une taille de texte pour le texte dessiné à l'intérieur du rectangle.
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)

Étape 4 : Définir les emplacements des lignes et des colonnes

Les formes de cette application sont affichées dans deux colonnes et quatre lignes, en fonction des valeurs des dimensions configurées ci-dessus. Les calculs mathématiques ne font pas partie de cet atelier de programmation, mais vous pouvez les consulter lorsque vous copiez le code fourni dans cette étape.

  1. Configurez les coordonnées pour deux colonnes.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
  1. Ajoutez les coordonnées de chaque ligne, y compris la dernière ligne pour le texte transformé.
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
  1. Exécutez votre application. Elle devrait s'ouvrir sur un écran blanc vide sous le nom de l'application.

Dans onDraw(), vous appelez des méthodes pour dessiner sept rectangles rognés différents, comme le montre la capture d'écran de l'application ci-dessous. Les rectangles sont tous dessinés de la même manière. La seule différence réside dans les régions de clipping définies et dans leur emplacement sur l'écran.

L'algorithme utilisé pour dessiner les rectangles fonctionne comme indiqué dans le schéma et l'explication ci-dessous. En résumé, vous dessinez une série de rectangles en déplaçant l'origine de Canvas. En principe, cela comprend les étapes suivantes :

(1) Commencez par traduire Canvas à l'endroit où vous souhaitez dessiner le rectangle. Autrement dit, au lieu de calculer où le prochain rectangle et toutes les autres formes doivent être dessinés, vous déplacez l'origine de Canvas, c'est-à-dire son système de coordonnées.

(2) Vous dessinez ensuite le rectangle à la nouvelle origine du canevas. Autrement dit, vous dessinez les formes au même emplacement dans le système de coordonnées traduit. C'est beaucoup plus simple et légèrement plus efficace.

(3) Enfin, vous restaurez le Canvas à son Origin d'origine.

Voici l'algorithme que vous allez implémenter :

  1. Dans onDraw(), appelez une fonction pour remplir le Canvas avec la couleur d'arrière-plan grise et dessiner les formes d'origine.
  2. Appelez une fonction pour chaque rectangle rogné et le texte à dessiner.

Pour chaque rectangle ou texte :

  1. Enregistrez l'état actuel de Canvas pour pouvoir le rétablir.
  2. Traduisez le Origin du canevas à l'emplacement où vous souhaitez dessiner.
  3. Appliquez des formes et des chemins de masquage.
  4. Dessinez le rectangle ou le texte.
  5. Restaurez l'état de Canvas.

Étape : Remplacer onDraw()

  1. Remplacez onDraw() comme indiqué dans le code ci-dessous. Vous appelez une fonction pour chaque forme que vous dessinez, que vous implémenterez plus tard.
 override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        drawBackAndUnclippedRectangle(canvas)
        drawDifferenceClippingExample(canvas)
        drawCircularClippingExample(canvas)
        drawIntersectionClippingExample(canvas)
        drawCombinedClippingExample(canvas)
        drawRoundedRectangleClippingExample(canvas)
        drawOutsideClippingExample(canvas)
        drawSkewedTextExample(canvas)
        drawTranslatedTextExample(canvas)
        // drawQuickRejectExample(canvas)
    }
  1. Créez des stubs pour chacune des fonctions de dessin afin que le code continue à se compiler. Vous pouvez copier le code ci-dessous.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
}
private fun drawDifferenceClippingExample(canvas: Canvas){
}
private fun drawCircularClippingExample(canvas: Canvas){
}
private fun drawIntersectionClippingExample(canvas: Canvas){
}
private fun drawCombinedClippingExample(canvas: Canvas){
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas){
}
private fun drawOutsideClippingExample(canvas: Canvas){
}
private fun drawTranslatedTextExample(canvas: Canvas){
}
private fun drawSkewedTextExample(canvas: Canvas){
}
private fun drawQuickRejectExample(canvas: Canvas){
}

L'application dessine le même rectangle et les mêmes formes sept fois : la première fois sans écrêtage, puis six fois avec différents chemins d'écrêtage appliqués. La méthode drawClippedRectangle() factorise le code permettant de dessiner un rectangle, comme indiqué ci-dessous.

Étape 1 : Créez la méthode drawClippedRectangle()

  1. Créez une méthode drawClippedRectangle() qui reçoit un argument canvas de type Canvas.
private fun drawClippedRectangle(canvas: Canvas) {
}
  1. Dans la méthode drawClippedRectangle(), définissez les limites du rectangle de clipping pour l'ensemble de la forme. Appliquez un rectangle de découpage qui limite le dessin au carré.
canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
)

La méthode Canvas.clipRect(...) réduit la région de l'écran dans laquelle les futures opérations de dessin peuvent écrire. Il définit les limites de clipping comme étant l'intersection spatiale du rectangle de clipping actuel et du rectangle transmis à clipRect(). Il existe de nombreuses variantes de la méthode clipRect() qui acceptent différentes formes pour les régions et autorisent différentes opérations sur le rectangle de clipping.

  1. Remplissez le canvas avec la couleur blanche. Oui. L'ensemble du canevas, car vous ne dessinez pas de rectangles, vous les découpez ! En raison du rectangle de clipping, seule la région définie par le rectangle de clipping est remplie, ce qui crée un rectangle blanc. Le reste de la surface reste gris.
canvas.drawColor(Color.WHITE)
  1. Définissez la couleur sur rouge et tracez une ligne diagonale à l'intérieur du rectangle de masquage.
paint.color = Color.RED
canvas.drawLine(
   clipRectLeft,clipRectTop,
   clipRectRight,clipRectBottom,paint
)
  1. Définissez la couleur sur vert et dessinez un cercle à l'intérieur du rectangle de clipping.
paint.color = Color.GREEN
canvas.drawCircle(
   circleRadius,clipRectBottom - circleRadius,
   circleRadius,paint
)
  1. Définissez la couleur sur bleu et dessinez du texte aligné sur le bord droit du rectangle de clipping. Utilisez canvas.drawText() pour dessiner du texte.
paint.color = Color.BLUE
// Align the RIGHT side of the text with the origin.
paint.textSize = textSize
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(
   context.getString(R.string.clipping),
   clipRectRight,textOffset,paint
)

Étape 2 : Implémentez la méthode drawBackAndUnclippedRectangle()

  1. Pour voir la méthode drawClippedRectangle() en action, dessinez le premier rectangle non coupé en implémentant la méthode drawBackAndUnclippedRectangle() comme indiqué ci-dessous. Enregistrez le canvas, traduisez-le dans la première ligne et la première colonne, dessinez-le en appelant drawClippedRectangle(), puis restaurez le canvas dans son état précédent.
private fun drawBackAndUnclippedRectangle(canvas: Canvas){
   canvas.drawColor(Color.GRAY)
   canvas.save()
   canvas.translate(columnOne,rowOne)
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Exécutez votre application. Vous devriez voir le premier rectangle blanc avec son cercle, sa ligne rouge et son texte sur un fond gris.

Dans les exemples de découpage suivants, vous appliquez différentes combinaisons de régions de découpage pour obtenir des effets graphiques et apprendre à combiner des régions de découpage afin de créer la forme dont vous avez besoin.

Chacune de ces méthodes suit le même modèle.

  1. Enregistrer l'état actuel du canevas : canvas.save()

Le contexte d'activité conserve une pile d'états de dessin. Les états de dessin se composent de la matrice de transformation actuelle et de la région de clipping actuelle. Vous pouvez enregistrer l'état actuel, effectuer des actions qui modifient l'état du dessin (comme translater ou faire pivoter le canevas), puis restaurer l'état du dessin enregistré. (Remarque : Cela revient à utiliser la commande "stash" dans git.)

Lorsque votre dessin inclut des transformations, il est difficile d'enchaîner et d'annuler des transformations en les inversant. Par exemple, si vous traduisez, étirez, puis faites pivoter, cela devient rapidement complexe. Enregistrez plutôt l'état du canevas, appliquez vos transformations, dessinez, puis restaurez l'état précédent.

Par exemple, vous pouvez définir une région de clipping et enregistrer cet état. Ensuite, traduisez le canevas, ajoutez une région de clipping et faites-le pivoter. Après avoir dessiné, vous pouvez restaurer l'état de clipping d'origine et effectuer une autre transformation de translation et d'inclinaison, comme illustré dans le diagramme.

  1. Translate the origin of the canvas to the row/column coordinates: canvas.translate()

Il est beaucoup plus simple de déplacer l'origine du canevas et de dessiner la même chose dans un nouveau système de coordonnées que de déplacer tous les éléments à dessiner. (Conseil : Vous pouvez utiliser la même technique pour faire pivoter des éléments.)

  1. Appliquez les transformations à path, le cas échéant.
  2. Appliquer le clipping : canvas.clipPath(path)
  3. Dessinez les formes : drawClippedRectangle() or drawText()
  4. Restaurez l'état précédent du canevas : canvas.restore()

Étape 1 : Implémenter drawDifferenceClippingExample(canvas)

Ajoutez du code pour dessiner le deuxième rectangle, qui utilise la différence entre deux rectangles de masquage pour créer un effet de cadre photo.

Utilisez le code ci-dessous, qui effectue les opérations suivantes :

  1. Enregistrez le canevas.
  2. Translate the origin of the canvas into open space to the first row, second column, to the right of the first rectangle.
  3. Appliquez deux rectangles de masquage. L'opérateur DIFFERENCE soustrait le deuxième rectangle au premier.
  1. Appelez la méthode drawClippedRectangle() pour dessiner le canevas modifié.
  2. Restaurez l'état du canevas.
private fun drawDifferenceClippingExample(canvas: Canvas) {
   canvas.save()
   // Move the origin to the right for the next rectangle.
   canvas.translate(columnTwo,rowOne)
   // Use the subtraction of two clipping rectangles to create a frame.
   canvas.clipRect(
       2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .DIFFERENCE) was deprecated in API level 26. The recommended
   // alternative method is clipOutRect(float, float, float, float),
   // which is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
       canvas.clipRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset,
            Region.Op.DIFFERENCE
       )
   } else {
       canvas.clipOutRect(
           4 * rectInset,4 * rectInset,
           clipRectRight - 4 * rectInset,
           clipRectBottom - 4 * rectInset
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}
  1. Exécutez votre application. Elle devrait ressembler à ceci.

Étape 2 : Implémenter drawCircularClippingExample(canvas)

Ensuite, ajoutez du code pour dessiner un rectangle qui utilise une région de clipping circulaire créée à partir d'un chemin circulaire. Cela permet d'effacer (et non de dessiner) le cercle et d'afficher ainsi l'arrière-plan gris.

private fun drawCircularClippingExample(canvas: Canvas) {

   canvas.save()
   canvas.translate(columnOne, rowTwo)
   // Clears any lines and curves from the path but unlike reset(),
   // keeps the internal data structure for faster reuse.
   path.rewind()
   path.addCircle(
       circleRadius,clipRectBottom - circleRadius,
       circleRadius,Path.Direction.CCW
   )
   // The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
   // API level 26. The recommended alternative method is
   // clipOutPath(Path), which is currently available in
   // API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipPath(path, Region.Op.DIFFERENCE)
   } else {
       canvas.clipOutPath(path)
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

Étape 3 : Implémenter drawIntersectionClippingExample(canvas)

Ajoutez ensuite du code pour dessiner l'intersection de deux rectangles de clipping dans la deuxième ligne et colonne.

Notez que l'apparence de cette région varie en fonction de la résolution de votre écran. Testez la dimension smallRectOffset pour modifier la taille de la région visible. Plus la valeur de smallRectOffset est petite, plus la région à l'écran est grande.

private fun drawIntersectionClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowTwo)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight - smallRectOffset,
       clipRectBottom - smallRectOffset
   )
   // The method clipRect(float, float, float, float, Region.Op
   // .INTERSECT) was deprecated in API level 26. The recommended
   // alternative method is clipRect(float, float, float, float), which
   // is currently available in API level 26 and higher.
   if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom,
           Region.Op.INTERSECT
       )
   } else {
       canvas.clipRect(
           clipRectLeft + smallRectOffset,
           clipRectTop + smallRectOffset,
           clipRectRight,clipRectBottom
       )
   }
   drawClippedRectangle(canvas)
   canvas.restore()
}

Étape 4 : Implémenter drawCombinedClippingExample(canvas)

Ensuite, combinez des formes (un cercle et un rectangle) et dessinez un chemin pour définir une région de masquage.

private fun drawCombinedClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne, rowThree)
   path.rewind()
   path.addCircle(
       clipRectLeft + rectInset + circleRadius,
       clipRectTop + circleRadius + rectInset,
       circleRadius,Path.Direction.CCW
   )
   path.addRect(
       clipRectRight / 2 - circleRadius,
       clipRectTop + circleRadius + rectInset,
       clipRectRight / 2 + circleRadius,
       clipRectBottom - rectInset,Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Étape 5 : Implémenter drawRoundedRectangleClippingExample(canvas)

Ajoutez ensuite un rectangle arrondi, qui est une forme de découpage couramment utilisée.

  1. Au niveau supérieur, créez et initialisez une variable de rectangle. RectF est une classe qui contient les coordonnées du rectangle en virgule flottante.
private var rectF = RectF(
   rectInset,
   rectInset,
   clipRectRight - rectInset,
   clipRectBottom - rectInset
)
  1. Implémentez la fonction drawRoundedRectangleClippingExample(). La fonction addRoundRect() prend un rectangle, des valeurs pour les valeurs x et y du rayon d'angle, et la direction pour enrouler le contour du rectangle arrondi. Path.Direction spécifie l'orientation des formes fermées (rectangles, ovales, etc.) lorsqu'elles sont ajoutées à un chemin. CCW est la forme abrégée de "counter-clockwise" (dans le sens inverse des aiguilles d'une montre).
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnTwo,rowThree)
   path.rewind()
   path.addRoundRect(
       rectF,clipRectRight / 4,
       clipRectRight / 4, Path.Direction.CCW
   )
   canvas.clipPath(path)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Étape 6 : Implémenter drawOutsideClippingExample(canvas)

Découpez l'extérieur autour du rectangle en doublant les marges intérieures du rectangle de découpage.

private fun drawOutsideClippingExample(canvas: Canvas) {
   canvas.save()
   canvas.translate(columnOne,rowFour)
   canvas.clipRect(2 * rectInset,2 * rectInset,
       clipRectRight - 2 * rectInset,
       clipRectBottom - 2 * rectInset)
   drawClippedRectangle(canvas)
   canvas.restore()
}

Étape 7 : Implémenter drawTranslatedTextExample(canvas)

Le dessin de texte ne diffère pas vraiment des autres formes. Vous pouvez appliquer des transformations au texte. Par exemple, vous pouvez traduire du texte en traduisant le canevas et en dessinant le texte.

  1. Implémentez la fonction ci-dessous.
private fun drawTranslatedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.GREEN
   // Align the RIGHT side of the text with the origin.
   paint.textAlign = Paint.Align.LEFT
   // Apply transformation to canvas.
   canvas.translate(columnTwo,textRow)
   // Draw text.
   canvas.drawText(context.getString(R.string.translated),
       clipRectLeft,clipRectTop,paint)
   canvas.restore()
}
  1. Exécutez votre application pour voir le texte traduit.

Étape 8 : Implémenter drawSkewedTextExample(canvas)

Vous pouvez également incliner du texte. Autrement dit, le déformer de différentes manières.

  1. Créez la fonction ci-dessous dans ClippedView.
private fun drawSkewedTextExample(canvas: Canvas) {
   canvas.save()
   paint.color = Color.YELLOW
   paint.textAlign = Paint.Align.RIGHT
   // Position text.
   canvas.translate(columnTwo, textRow)
   // Apply skew transformation.
   canvas.skew(0.2f, 0.3f)
   canvas.drawText(context.getString(R.string.skewed),
       clipRectLeft, clipRectTop, paint)
   canvas.restore()
}
  1. Exécutez votre application pour voir le texte incliné dessiné avant le texte traduit.

La méthode quickReject() Canvas vous permet de vérifier si un rectangle ou un chemin spécifié se trouve complètement en dehors des régions actuellement visibles, une fois toutes les transformations appliquées.

La méthode quickReject() est incroyablement utile lorsque vous créez des dessins plus complexes et que vous devez le faire le plus rapidement possible. Avec quickReject(), vous pouvez décider efficacement quels objets vous n'avez pas besoin de dessiner du tout, et il n'est pas nécessaire d'écrire votre propre logique d'intersection.

  • La méthode quickReject() renvoie true si le rectangle ou le chemin ne sont pas visibles à l'écran. Pour les chevauchements partiels, vous devez toujours effectuer vos propres vérifications.
  • EdgeType est AA (Antialiased : traiter les bords en les arrondissant, car ils peuvent être anticrénelés) ou BW (Black-White : traiter les bords en les arrondissant simplement à la limite de pixel la plus proche) pour un simple arrondi au pixel le plus proche.

Il existe plusieurs versions de quickReject(), que vous pouvez également trouver dans la documentation.

boolean

quickReject(float left, float top, float right, float bottom, Canvas.EdgeType type)

boolean

quickReject(RectF rect, Canvas.EdgeType type)

boolean

quickReject(Path path, Canvas.EdgeType type)

Dans cet exercice, vous allez dessiner une nouvelle ligne sous le texte et à l'intérieur de clipRect, comme précédemment.

  • Vous appelez d'abord quickReject() avec un rectangle inClipRectangle qui chevauche clipRect. Par conséquent, quickReject() renvoie "false", clipRect est rempli avec BLACK et le rectangle inClipRectangle est dessiné.

  • Modifiez ensuite le code et appelez quickReject() avec notInClipRectangle. quickReject() renvoie maintenant "true", clipRect est rempli avec WHITE et notInClipRectangle n'est pas dessiné.

Lorsque vous avez des dessins complexes, cela peut vous indiquer rapidement quelles formes sont complètement en dehors de la région de clipping et pour lesquelles vous devrez peut-être effectuer des calculs et des dessins supplémentaires, car elles se trouvent partiellement ou entièrement à l'intérieur de la région de clipping.

Étape : Effectuer des tests avec quickReject()

  1. Au niveau supérieur, créez une variable pour les coordonnées y d'une ligne supplémentaire.
   private val rejectRow = rowFour + rectInset + 2*clipRectBottom
  1. Ajoutez la fonction drawQuickRejectExample() suivante à ClippedView. Lisez le code, car il contient tout ce que vous devez savoir pour utiliser quickReject().
private fun drawQuickRejectExample(canvas: Canvas) {
   val inClipRectangle = RectF(clipRectRight / 2,
       clipRectBottom / 2,
       clipRectRight * 2,
       clipRectBottom * 2)

   val notInClipRectangle = RectF(RectF(clipRectRight+1,
       clipRectBottom+1,
       clipRectRight * 2,
       clipRectBottom * 2))

   canvas.save()
   canvas.translate(columnOne, rejectRow)
   canvas.clipRect(
       clipRectLeft,clipRectTop,
       clipRectRight,clipRectBottom
   )
   if (canvas.quickReject(
           inClipRectangle, Canvas.EdgeType.AA)) {
       canvas.drawColor(Color.WHITE)
   }
   else {
       canvas.drawColor(Color.BLACK)
       canvas.drawRect(inClipRectangle, paint
       )
   }
       canvas.restore()
}
  1. Dans onDraw(), annulez la mise en commentaire de l'appel de drawQuickRejectExample().
  2. Exécutez votre application. Vous verrez un rectangle noir, qui correspond à la région de clipping remplie, et des parties de inClipRectangle, car les deux rectangles se chevauchent. quickReject() renvoie donc false et inClipRectangle est dessiné.

  1. Dans drawQuickRejectExample(), modifiez le code pour exécuter quickReject() par rapport à notInClipRectangle.. quickReject() renvoie désormais true et la région de clipping est remplie en blanc.

Téléchargez le code de l'atelier de programmation terminé.

$  git clone https://github.com/googlecodelabs/android-kotlin-drawing-clipping


Vous pouvez également télécharger le dépôt sous forme de fichier ZIP, le décompresser et l'ouvrir dans Android Studio.

Télécharger le fichier ZIP

  • Le Context d'une activité conserve un état qui préserve les transformations et les régions de clipping pour le Canvas.
  • Utilisez canvas.save() et canvas.restore() pour dessiner et revenir à l'état d'origine de votre canevas.
  • Pour dessiner plusieurs formes sur un canevas, vous pouvez calculer leur emplacement ou déplacer (transférer) l'origine de votre surface de dessin. Ce dernier peut faciliter la création de méthodes utilitaires pour les séquences de dessin répétées.
  • Les régions de masquage peuvent être de n'importe quelle forme, combinaison de formes ou chemin.
  • Vous pouvez ajouter, soustraire et croiser des régions de clipping pour obtenir exactement la région dont vous avez besoin.
  • Vous pouvez appliquer des transformations au texte en transformant le canevas.
  • La méthode quickReject() Canvas vous permet de vérifier si un rectangle ou un chemin d'accès spécifié se trouve entièrement en dehors des régions actuellement visibles.

Cours Udacity :

Documentation pour les développeurs Android :

Consultez également la série d'articles sur l'architecture graphique pour obtenir une explication détaillée de la façon dont le framework Android s'affiche à l'écran.

Cette section répertorie les devoirs possibles pour les élèves qui suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :

  • Attribuer des devoirs si nécessaire
  • Indiquer aux élèves comment rendre leurs devoirs
  • Noter les devoirs

Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.

Si vous suivez cet atelier de programmation par vous-même, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.

Répondre aux questions suivantes

Question 1

Quelle méthode appelez-vous pour exclure efficacement les formes du dessin ?

▢ excludeFromDrawing()

▢ quickReject()

▢ onDraw()

▢ clipRect()

Question 2

Quelles informations sont enregistrées et restaurées par Canvas.save() et Canvas.restore() ?

▢ Couleur, épaisseur de la ligne, etc.

▢ Transformations actuelles uniquement

▢ Transformations actuelles et région de clipping

▢ Région de découpage actuelle uniquement

Question 3

Paint.Align spécifie les éléments suivants :

▢ Aligner les formes de dessin suivantes

▢ Côté de l'origine à partir duquel le texte est dessiné

▢ Où il est aligné dans la région de clipping

▢ Côté du texte à aligner sur l'origine

Pour accéder aux autres ateliers de programmation de ce cours, consultez la page de destination des ateliers de programmation "Développement Android avancé en Kotlin".