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 remplaceronDraw()
etonSizeChanged()
.
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
- Créez un projet Kotlin appelé
ClippingExample
avec le modèle Empty Activity (Activité vide). Utilisezcom.example.android
comme préfixe du nom du package. - Ouvrez
MainActivity.kt
. - Dans la méthode
onCreate()
, remplacez la vue de contenu par défaut et définissez la vue de contenu sur une nouvelle instance deClippedView
. Il s'agira de votre vue personnalisée pour les exemples d'écrêtage que vous allez créer ensuite.
setContentView(ClippedView(this))
- Au même niveau que
MainActivity.kt
, créez un fichier et une classe Kotlin pour une vue personnalisée appeléeClippedView
qui étendView
. Attribuez-lui la signature ci-dessous. Le reste de votre travail se fera dans ceClippedView
. 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
- 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.
- Dans Android Studio, effectuez un clic droit sur le dossier values, puis sélectionnez New > Values resource file (Nouveau > Fichier de ressources de valeurs).
- 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.
- Le fichier doit s'afficher dans votre dossier de valeurs, comme indiqué ci-dessous.
- 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
.
- 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>
- 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
- Revenez à la vue Android de votre projet.
- Dans
ClippedView
, définissez une variablePaint
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)
}
- Dans
ClippedView
, créez et initialisez unPath
pour stocker localement le chemin de ce qui a été dessiné. Importezandroid.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.
- Dans
ClippedView
, souspath
, 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)
- 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)
- 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)
- 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.
- Configurez les coordonnées pour deux colonnes.
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
- 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)
- 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 :
- Dans
onDraw()
, appelez une fonction pour remplir leCanvas
avec la couleur d'arrière-plan grise et dessiner les formes d'origine. - Appelez une fonction pour chaque rectangle rogné et le texte à dessiner.
Pour chaque rectangle ou texte :
- Enregistrez l'état actuel de
Canvas
pour pouvoir le rétablir. - Traduisez le
Origin
du canevas à l'emplacement où vous souhaitez dessiner. - Appliquez des formes et des chemins de masquage.
- Dessinez le rectangle ou le texte.
- Restaurez l'état de
Canvas
.
Étape : Remplacer onDraw()
- 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)
}
- 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()
- Créez une méthode
drawClippedRectangle()
qui reçoit un argumentcanvas
de typeCanvas
.
private fun drawClippedRectangle(canvas: Canvas) {
}
- 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.
- 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)
- 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
)
- 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
)
- 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()
- Pour voir la méthode
drawClippedRectangle()
en action, dessinez le premier rectangle non coupé en implémentant la méthodedrawBackAndUnclippedRectangle()
comme indiqué ci-dessous. Enregistrez lecanvas
, traduisez-le dans la première ligne et la première colonne, dessinez-le en appelantdrawClippedRectangle()
, puis restaurez lecanvas
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()
}
- 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.
- 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.
- 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.)
- Appliquez les transformations à
path
, le cas échéant. - Appliquer le clipping :
canvas.clipPath(path)
- Dessinez les formes :
drawClippedRectangle() or drawText()
- 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 :
- Enregistrez le canevas.
- Translate the origin of the canvas into open space to the first row, second column, to the right of the first rectangle.
- Appliquez deux rectangles de masquage. L'opérateur
DIFFERENCE
soustrait le deuxième rectangle au premier.
- Appelez la méthode
drawClippedRectangle()
pour dessiner le canevas modifié. - 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()
}
- 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.
- 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
)
- Implémentez la fonction
drawRoundedRectangleClippingExample()
. La fonctionaddRoundRect()
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.
- 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()
}
- 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.
- 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()
}
- 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()
renvoietrue
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
estAA
(Antialiased : traiter les bords en les arrondissant, car ils peuvent être anticrénelés) ouBW
(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.
| quickReject |
| quickReject |
| quickReject |
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 rectangleinClipRectangle
qui chevaucheclipRect
. Par conséquent,quickReject()
renvoie "false",clipRect
est rempli avecBLACK
et le rectangleinClipRectangle
est dessiné.
- Modifiez ensuite le code et appelez
quickReject()
avecnotInClipRectangle
.quickReject()
renvoie maintenant "true",clipRect
est rempli avecWHITE
etnotInClipRectangle
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()
- 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
- Ajoutez la fonction
drawQuickRejectExample()
suivante àClippedView
. Lisez le code, car il contient tout ce que vous devez savoir pour utiliserquickReject()
.
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()
}
- Dans
onDraw()
, annulez la mise en commentaire de l'appel dedrawQuickRejectExample()
. - 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 doncfalse
etinClipRectangle
est dessiné.
- Dans
drawQuickRejectExample()
, modifiez le code pour exécuterquickReject()
par rapport ànotInClipRectangle.
.quickReject()
renvoie désormaistrue
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.
- Le
Context
d'une activité conserve un état qui préserve les transformations et les régions de clipping pour leCanvas
. - Utilisez
canvas.save()
etcanvas.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 :
- Classe
Canvas
- Classe
Bitmap
- Classe
View
- Classe
Paint
- Configurations
Bitmap.config
- Opérateurs
Region.Op
- Classe
Path
- Classe
Canvas
- Classe
Bitmap
- Classe
View
- Classe
Paint
- Configurations
Bitmap.config
- Opérateurs
Region.Op
- Classe
Path
- Outils de graphisme
android.graphics
- Configurations
Bitmap.Config
Canvas
- Canevas et drawables
- Que fait canvas.translate() ?
- Comprendre save() et restore() pour le contexte Canvas
- clipping
- Surtracé.
@JvmOverloads
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".