Das Wichtigste bei einer benutzerdefinierten Ansicht ist ihre Darstellung. Benutzerdefinierte Zeichnung je nach Anwendungsanforderungen einfach oder komplex sein können. Dieses Dokument deckt einige der gängigsten Operationen ab.
Weitere Informationen finden Sie unter Drawables – Übersicht
onDraw() überschreiben
Der wichtigste Schritt beim Zeichnen einer benutzerdefinierten Ansicht besteht darin,
onDraw()
. Der Parameter für onDraw()
ist ein
Canvas
-Objekt, das zum Zeichnen der Ansicht verwendet werden kann. Klasse Canvas
definiert Methoden zum Zeichnen von Text, Linien, Bitmaps und vielen anderen Grafiken
Primitiven. Sie können diese Methoden in onDraw()
verwenden, um Folgendes zu erstellen:
benutzerdefinierte Benutzeroberfläche.
Erstellen Sie zuerst ein
Paint
-Objekt.
Im nächsten Abschnitt wird Paint
ausführlicher behandelt.
Zeichenobjekte erstellen
Die
android.graphics
teilt das Zeichnen in zwei Bereiche auf:
- Was gezeichnet werden soll, verarbeitet von
Canvas
. - Wie gezeichnet wird, verarbeitet von
Paint
.
Beispielsweise bietet Canvas
eine Methode zum Zeichnen einer Linie.
Paint
stellt Methoden zum Definieren der Linienfarbe bereit.
Canvas
hat eine Methode zum Zeichnen eines Rechtecks und Paint
definiert, ob das Rechteck mit einer Farbe gefüllt oder leer bleiben soll.
Canvas
definiert Formen, die Sie auf dem Bildschirm zeichnen können.
Mit Paint
werden Farbe, Stil, Schriftart usw. jeder Form definiert.
die Sie zeichnen.
Bevor Sie etwas zeichnen, erstellen Sie ein oder mehrere Paint
-Objekte. Die
Im folgenden Beispiel erfolgt dies in einer Methode namens init
. Diese Methode ist
vom Konstruktor aus Java aufgerufen, kann aber inline in
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)); ... }
Das Erstellen von Objekten im Voraus ist eine wichtige Optimierung. Aufrufe sind
und viele Zeichenobjekte eine aufwendige Initialisierung erfordern.
Erhebliche Erstellung von Zeichenobjekten in der onDraw()
-Methode
reduziert die Leistung und kann dazu führen, dass Ihre Benutzeroberfläche langsam ist.
Layoutereignisse verarbeiten
Um Ihre benutzerdefinierte Ansicht richtig zu zeichnen, müssen Sie ihre Größe ermitteln. Komplex (benutzerdefiniert) Für Ansichten müssen häufig mehrere Layoutberechnungen abhängig von der Größe durchgeführt werden. und die Form ihres Bildschirmbereichs. Treffen Sie niemals Annahmen über die Größe Ihrer auf dem Bildschirm. Auch wenn nur eine App Ihre Ansicht verwendet, muss diese verschiedene Bildschirmgrößen, unterschiedliche Bildschirmdichten und Seitenverhältnisse im Hoch- und im Querformat verwenden.
Obwohl View
gibt es viele Messmethoden, von denen die meisten nicht
überschrieben. Wenn für Ihre Ansicht keine spezielle Kontrolle über die Größe erforderlich ist,
eine Methode überschreiben:
onSizeChanged()
onSizeChanged()
wird aufgerufen, wenn Ihrer Ansicht zum ersten Mal ein
und auch dann, wenn sich die Größe der Ansicht aus irgendeinem Grund ändert. Berechnen
und andere Werte in Bezug auf die Größe der Ansicht in
onSizeChanged()
, anstatt sie bei jedem Zeichnen neu zu berechnen.
Im folgenden Beispiel ist die Ansicht onSizeChanged()
berechnet das Begrenzungsrahmen des Diagramms und die relative Position des
Textlabel und andere visuelle Elemente.
Wenn Ihrer Ansicht eine Größe zugewiesen wird, geht der Layout-Manager davon aus,
enthält den Abstand der Ansicht. Padding-Werte verarbeiten, wenn Sie Ihre
Ansicht anpassen. Hier ist ein Snippet von onSizeChanged()
, das zeigt,
Gehen Sie dazu so vor:
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); }
Wenn Sie eine genauere Kontrolle über die Layoutparameter Ihrer Ansicht benötigen, implementieren Sie
onMeasure()
Die Parameter dieser Methode sind
View.MeasureSpec
die angeben, wie groß Ihr Aufruf sein soll,
ob es sich dabei um ein festes Maximum oder nur um einen Vorschlag handelt. Zur Optimierung
Diese Werte werden als gepackte Ganzzahlen gespeichert und Sie verwenden die statischen Methoden der
View.MeasureSpec
, um die in jeder Ganzzahl gespeicherten Informationen zu entpacken.
Hier siehst du eine Beispielimplementierung von onMeasure()
. In dieser
wird versucht, den Bereich groß genug zu machen, damit das Diagramm
als Label verwenden:
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); }
Bei diesem Code sind drei wichtige Dinge zu beachten:
- Bei den Berechnungen wird auch der Abstand der Ansicht berücksichtigt. Wie bereits erwähnt ist dies in der Verantwortung der Ansicht.
- Die Hilfsmethode
resolveSizeAndState()
wird verwendet, um die endgültigen Werte für Breite und Höhe zu erstellen. Dieses Hilfsprogramm gibt einen geeignetenView.MeasureSpec
-Wert, indem Sie die der Ansicht auf den anonMeasure()
übergebenen Wert. onMeasure()
hat keinen Rückgabewert. Stattdessen wird die Methode kommuniziert seine Ergebnisse mit einem AufrufsetMeasuredDimension()
Das Aufrufen dieser Methode ist obligatorisch. Wenn Sie diesen Aufruf weglassen, Die KlasseView
gibt eine Laufzeitausnahme aus.
Zeichnen
Nachdem Sie den Code zur Objekterstellung und Messung definiert haben, können Sie
onDraw()
onDraw()
wird in jeder Ansicht unterschiedlich implementiert,
Die meisten Ansichten weisen jedoch einige gängige Vorgänge auf:
- Text zeichnen mit
drawText()
Geben Sie das Schriftbild an, indem SiesetTypeface()
und die Textfarbe durch Aufrufen vonsetColor()
. - Primitive Formen zeichnen mit
drawRect()
,drawOval()
, unddrawArc()
Sie können festlegen, ob die Formen gefüllt, umrissen oder beides sind. Rufen Sie dazusetStyle()
- Zeichnen Sie komplexere Formen mithilfe der
Path
. Form definieren, indem Sie einemPath
Linien und Kurven hinzufügen und zeichnen Sie die FormdrawPath()
. Wie bei primitiven Formen können Pfade umrissen, ausgefüllt oder beides sein. abhängig vonsetStyle()
. -
Farbverlaufsfüllungen durch Erstellen von
LinearGradient
definieren Objekte. AnrufsetShader()
umLinearGradient
für ausgefüllte Formen zu verwenden. - Bitmaps zeichnen mit
drawBitmap()
Mit dem folgenden Code wird eine Mischung aus Text, Linien und Formen gezeichnet:
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; }
Grafikeffekte anwenden
Android 12 (API-Level 31) fügt die
RenderEffect
, in der gängige Grafikeffekte wie Weichzeichner, Farbfilter,
Android-Shader-Effekte und mehr,
View
-Objekte und
und Rendering von Hierarchien. Du kannst Effekte als Ketteneffekte kombinieren.
inneren und äußeren Effekten oder kombinierte Effekte. Unterstützung für diese Funktion
variiert je nach Prozessorleistung des Geräts.
Sie können auch Effekte auf die zugrunde liegende
RenderNode
für
View
durch Aufrufen von
View.setRenderEffect(RenderEffect)
So implementierst du ein RenderEffect
-Objekt:
view.setRenderEffect(RenderEffect.createBlurEffect(radiusX, radiusY, SHADER_TILE_MODE))
Sie können die Ansicht programmatisch erstellen oder über ein XML-Layout erweitern und
rufen Sie ihn mit View Binding (Bindung ansehen) ab oder
findViewById()