La partie la plus importante d'une vue personnalisée est son apparence. Dessin personnalisé peuvent être simples ou complexes selon les besoins de votre application. Ce document certaines des opérations les plus courantes.
Pour en savoir plus, consultez Présentation des drawables
Ignorer onDraw()
L'étape la plus importante du dessin d'une vue personnalisée consiste à remplacer la
onDraw()
. Le paramètre de onDraw()
est un
Canvas
que la vue peut utiliser pour elle-même. La classe Canvas
définit des méthodes pour dessiner du texte, des lignes, des bitmaps et de nombreux autres éléments graphiques.
primitives. Vous pouvez utiliser ces méthodes dans onDraw()
pour créer vos
personnalisée.
Commencez par créer un
Paint
.
La section suivante aborde Paint
plus en détail.
Créer des objets de dessin
La
android.graphics
divise le dessin en deux zones:
- Objet à dessiner, géré par
Canvas
. - Comment dessiner, géré par
Paint
.
Par exemple, Canvas
fournit une méthode pour tracer une ligne.
Paint
fournit des méthodes pour définir la couleur de cette ligne.
Canvas
dispose d'une méthode pour dessiner un rectangle, et Paint
définit s'il faut remplir ce rectangle avec une couleur ou le laisser vide.
Canvas
définit les formes que vous pouvez dessiner à l'écran.
Paint
définit la couleur, le style, la police, etc. de chaque forme
lorsque vous dessinez.
Avant de dessiner, créez un ou plusieurs objets Paint
. La
L'exemple suivant le fait dans une méthode appelée init
. Cette méthode est
appelé à partir du constructeur depuis Java, mais il peut être initialisé de manière intégrée dans
Kotlin.
Kotlin
@ColorInt private var textColor // Obtained from style attributes. @Dimension private var textHeight // Obtained from style attributes. private val textPaint = Paint(ANTI_ALIAS_FLAG).apply { color = textColor if (textHeight == 0f) { textHeight = textSize } else { textSize = textHeight } } private val piePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL textSize = textHeight } private val shadowPaint = Paint(0).apply { color = 0x101010 maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL) }
Java
private Paint textPaint; private Paint piePaint; private Paint shadowPaint; @ColorInt private int textColor; // Obtained from style attributes. @Dimension private float textHeight; // Obtained from style attributes. private void init() { textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(textColor); if (textHeight == 0) { textHeight = textPaint.getTextSize(); } else { textPaint.setTextSize(textHeight); } piePaint = new Paint(Paint.ANTI_ALIAS_FLAG); piePaint.setStyle(Paint.Style.FILL); piePaint.setTextSize(textHeight); shadowPaint = new Paint(0); shadowPaint.setColor(0xff101010); shadowPaint.setMaskFilter(new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL)); ... }
Créer des objets à l'avance est une optimisation importante. Les vues sont
redessinées fréquemment, et de nombreux objets de dessin nécessitent une initialisation coûteuse.
Création importante d'objets de dessin dans la méthode onDraw()
ce qui réduit les performances et peut ralentir l'interface utilisateur.
Gérer les événements de mise en page
Pour dessiner correctement votre vue personnalisée, déterminez sa taille. Personnalisé complexe les vues doivent souvent effectuer plusieurs calculs de mise en page en fonction de la taille et la forme de sa zone à l'écran. Ne faites jamais de suppositions sur la taille à l'écran. Même si une seule application utilise votre vue, cette application doit gèrent différentes tailles et densités d'écran, en mode portrait et paysage.
Bien que View
propose de nombreuses méthodes pour gérer les mesures, la plupart n'ont pas besoin d'être
être remplacées. Si vous n'avez pas besoin d'exercer un contrôle spécial sur la taille de la vue,
ignorer une méthode:
onSizeChanged()
onSizeChanged()
est appelé lorsque vous recevez pour la première fois un
et encore une fois si la taille de la vue change pour une raison quelconque. Calculer
les positions, les dimensions et toute autre valeur liée à la taille de la vue dans
onSizeChanged()
, au lieu de les recalculer chaque fois que vous dessinez.
Dans l'exemple suivant, onSizeChanged()
est l'endroit où la vue
calcule le rectangle de délimitation du graphique et la position relative du
une étiquette de texte et
d'autres éléments visuels.
Lorsque vous attribuez une taille à votre vue, le gestionnaire de mises en page suppose que cette taille
inclut la marge intérieure de la vue. Gérez les valeurs de marge intérieure lorsque vous calculez votre
la taille de l'affichage. Voici un extrait de onSizeChanged()
qui montre comment
en procédant comme suit:
Kotlin
private val showText // Obtained from styled attributes. private val textWidth // Obtained from styled attributes. override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) // Account for padding. var xpad = (paddingLeft + paddingRight).toFloat() val ypad = (paddingTop + paddingBottom).toFloat() // Account for the label. if (showText) xpad += textWidth.toFloat() val ww = w.toFloat() - xpad val hh = h.toFloat() - ypad // Figure out how big you can make the pie. val diameter = Math.min(ww, hh) }
Java
private Boolean showText; // Obtained from styled attributes. private int textWidth; // Obtained from styled attributes. @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Account for padding. float xpad = (float)(getPaddingLeft() + getPaddingRight()); float ypad = (float)(getPaddingTop() + getPaddingBottom()); // Account for the label. if (showText) xpad += textWidth; float ww = (float)w - xpad; float hh = (float)h - ypad; // Figure out how big you can make the pie. float diameter = Math.min(ww, hh); }
Si vous avez besoin d'un contrôle plus précis sur les paramètres de mise en page de votre vue, implémentez
onMeasure()
Les paramètres de cette méthode
View.MeasureSpec
qui indiquent la taille souhaitée par le parent de la vue, et
qu'il s'agisse d'un maximum strict ou d'une simple suggestion. À titre d'optimisation,
ces valeurs sont stockées sous forme d'entiers empaquetés et vous utilisez les méthodes statiques
View.MeasureSpec
pour désimbriquer les informations stockées dans chaque entier.
Voici un exemple d'implémentation de onMeasure()
. Dans ce
il essaie de faire en sorte que le graphique soit aussi grand
comme libellé:
Kotlin
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // Try for a width based on your minimum. val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth val w: Int = View.resolveSizeAndState(minw, widthMeasureSpec, 1) // Whatever the width is, ask for a height that lets the pie get as big as // it can. val minh: Int = View.MeasureSpec.getSize(w) - textWidth.toInt() + paddingBottom + paddingTop val h: Int = View.resolveSizeAndState(minh, heightMeasureSpec, 0) setMeasuredDimension(w, h) }
Java
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on your minimum. int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width is, ask for a height that lets the pie get as big as it // can. int minh = MeasureSpec.getSize(w) - (int)textWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(minh, heightMeasureSpec, 0); setMeasuredDimension(w, h); }
Trois points importants sont à noter dans ce code:
- Les calculs tiennent compte de la marge intérieure de la vue. Comme indiqué précédemment il s'agit de la responsabilité de la vue.
- Méthode d'assistance
resolveSizeAndState()
est utilisé pour créer les valeurs finales de largeur et de hauteur. Cet outil d'aide renvoie une valeurView.MeasureSpec
appropriée en comparant les la taille requise de la vue à la valeur transmise àonMeasure()
. onMeasure()
n'a pas de valeur renvoyée. La méthode communique ses résultats en appelantsetMeasuredDimension()
Vous devez obligatoirement appeler cette méthode. Si vous omettez cet appel, le La classeView
génère une exception d'exécution.
Dessiner
Après avoir défini votre code de création d'objet et de mesure, vous pouvez implémenter
onDraw()
Chaque vue implémente onDraw()
différemment,
mais il existe quelques opérations courantes que la plupart des vues partagent:
- Dessinez du texte à l'aide de
drawText()
Spécifiez la police de caractères en appelantsetTypeface()
et la couleur du texte en appelantsetColor()
- Dessiner des formes primitives à l'aide de
drawRect()
,drawOval()
, etdrawArc()
Déterminez si les formes sont remplies, avec des contours ou les deux en appelant la méthodesetStyle()
- Dessiner des formes plus complexes à l'aide des commandes
Path
. Définissez une forme en ajoutant des lignes et des courbes à unePath
. puis tracez la forme avecdrawPath()
Comme pour les formes primitives, les tracés peuvent être tracés, remplis ou les deux, en fonction desetStyle()
. -
Définissez les remplissages en dégradé en créant
LinearGradient
d'objets. AppelersetShader()
pour utiliser votreLinearGradient
sur des formes remplies. - Dessiner des bitmaps à l'aide de
drawBitmap()
Le code suivant trace un mélange de texte, de lignes et de formes:
Kotlin
private val data = mutableListOf<Item>() // A list of items that are displayed. private var shadowBounds = RectF() // Calculated in onSizeChanged. private var pointerRadius: Float = 2f // Obtained from styled attributes. private var pointerX: Float = 0f // Calculated in onSizeChanged. private var pointerY: Float = 0f // Calculated in onSizeChanged. private var textX: Float = 0f // Calculated in onSizeChanged. private var textY: Float = 0f // Calculated in onSizeChanged. private var bounds = RectF() // Calculated in onSizeChanged. private var currentItem: Int = 0 // The index of the currently selected item. override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.apply { // Draw the shadow. drawOval(shadowBounds, shadowPaint) // Draw the label text. drawText(data[currentItem].label, textX, textY, textPaint) // Draw the pie slices. data.forEach {item -> piePaint.shader = item.shader drawArc( bounds, 360 - item.endAngle, item.endAngle - item.startAngle, true, piePaint ) } // Draw the pointer. drawLine(textX, pointerY, pointerX, pointerY, textPaint) drawCircle(pointerX, pointerY, pointerRadius, textPaint) } } // Maintains the state for a data item. private data class Item( var label: String, var value: Float = 0f, @ColorInt var color: Int = 0, // Computed values. var startAngle: Float = 0f, var endAngle: Float = 0f, var shader: Shader )
Java
private List<Item> data = new ArrayList<Item>(); // A list of items that are displayed. private RectF shadowBounds; // Calculated in onSizeChanged. private float pointerRadius; // Obtained from styled attributes. private float pointerX; // Calculated in onSizeChanged. private float pointerY; // Calculated in onSizeChanged. private float textX; // Calculated in onSizeChanged. private float textY; // Calculated in onSizeChanged. private RectF bounds; // Calculated in onSizeChanged. private int currentItem = 0; // The index of the currently selected item. protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the shadow. canvas.drawOval( shadowBounds, shadowPaint ); // Draw the label text. canvas.drawText(data.get(currentItem).label, textX, textY, textPaint); // Draw the pie slices. for (int i = 0; i < data.size(); ++i) { Item it = data.get(i); piePaint.setShader(it.shader); canvas.drawArc( bounds, 360 - it.endAngle, it.endAngle - it.startAngle, true, piePaint ); } // Draw the pointer. canvas.drawLine(textX, pointerY, pointerX, pointerY, textPaint); canvas.drawCircle(pointerX, pointerY, pointerRadius, textPaint); } // Maintains the state for a data item. private class Item { public String label; public float value; @ColorInt public int color; // Computed values. public int startAngle; public int endAngle; public Shader shader; }
Appliquer des effets graphiques
Android 12 (niveau d'API 31) ajoute la
RenderEffect
, qui applique des effets graphiques courants tels que le floutage, les filtres de couleur,
les effets du nuanceur Android, et plus encore
Objets View
et
de rendu. Vous pouvez combiner plusieurs effets sous forme d'effets de chaîne, qui consistent
d'un effet intérieur et extérieur, ou d'effets mixtes. Compatibilité avec cette fonctionnalité
varie en fonction de la puissance de traitement de l'appareil.
Vous pouvez aussi appliquer des effets aux
RenderNode
pour
un View
en appelant
View.setRenderEffect(RenderEffect)
Pour implémenter un objet RenderEffect
, procédez comme suit:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Vous pouvez créer la vue par programmation ou la gonfler à partir d'une mise en page XML et
à les récupérer à l'aide de la fonctionnalité View Binding .
findViewById()
.