Im letzten Blogpost über Hannibal Mobile haben wir darüber geschrieben wie wir Hannibal Mobile nach dem MVC-Muster strukturiert haben. Diesmal gehen wir tiefer auf die Views ein.

Android bietet bereits von Haus aus eine Palette an UI-Elementen. Aber manchmal braucht man halt eine Extrawurst und muss eigene Views bauen. Je nach Bedürfnis gibt es prinzipiell drei verschiedene Methoden, das anzugehen:

  1. Zusammensetzen von Views: Wenn man einfach Standardkomponenten zusammenstecken will, kann man von einer der Layout-Klassen erben und dann den eigenen Inhalt programmatisch via addView() hinzufügen oder ein XML-Layout über LayoutInflater laden.
  2. Anpassen von UI-Elementen: Wenn ein bestehendes UI-Element zumindest im Groben das macht was man will, kann man einfach davon erben und die onDraw()-Methode überschreiben.
  3. Eigene Implementation von View: Wenn man einen leeren Canvas zum Zeichnen haben will, kann man von View erben und alles selber machen.

Für das Dashboard in Hannibal Mobile haben wir alle drei Methoden eingesetzt um es nach unseren Wünschen aussehen zu lassen:

Hannibal mobile dashboard

Gauges im GridView – Beispiel für das Zusammensetzen von Views

Der Inhaltsbereich besteht aus einer GridView, die die “Gauges”, wie wir die Kacheln mit den Informationen nennen, in ein Gitter anordnet.

Ein Gauge

Wenn man sich die Gauges anschaut, wird schnell klar, was sie alle gemeinsam haben:

  • Hintergrund
  • Titel
  • Inhaltsbereich mit überlagerten Verlauf
  • Ladeanzeige solange die Daten noch nicht geladen sind

Diese vier Sachen lassen sich prima in eine Basisklasse stecken und den Inhaltsbereich den konkreten Gauges überlassen.

Das lässt sich ziemlich einfach implementieren: Wir haben einen BaseGauge, der vom FrameLayout ableitet. BaseGauge ist zuständig, das ganze Drumherum zu laden und für die konkreten Gauge-Implementationen einen Bereich in der View-Hierarchie zur Verfügung zu stellen, so dass diese ihren eigenen Inhalt an der richtigen Stelle darstellen können. Ausserdem verwaltet BaseGauge den Titel und Ladeanzeige.

Wenn man ein Gauge implementiert, muss man sich somit nur noch um den Gauge-Inhalt kümmern.

 

Runde Ecken – Beispiel für das Anpassen von UI-Elementen

Der Inhaltsbereich eines Gauges, wo sich der Graph und die Beschriftung befindet, hat runde Ecken und man muss aufpassen, nicht in die Ecken zu zeichnen. Am einfachsten erreicht man das, indem man eine eigene Layout-Klasse definiert.

Dazu kann man die dispatchDraw()-Methode überschreiben. dispatchDraw() ist dafür zuständig die Views, die im Layout enthalten sind, zu zeichnen. Da Layouts in der Regel keinen eigenen Inhalt haben, wird onDraw() nicht aufgerufen, ausser man verlangt das explizit.

Die Child-Views werden in ein separates Bitmap gezeichnet damit man danach die Eckenrundungen abschneiden können. Androids Canvas kann man zwar auf beliebige Formen begrenzen aber das hat den Nachteil, dass kein Antialiasing stattfindet. Das führt dazu, dass vor allem auf gröber auflösende Displays die Abrundungen treppenförmig aussehen.

Mal angenommen, renderedContent enthält dieses Kätzchen (aus welchen Gründe auch immer):

Jööh! (Schweizerdeutsch für “Ach wie süss!”)

Ohne Antialiasing würden die abgerundeten Ecken so aussehen:

Immer noch Jööh!, aber diese gezackten runden Ecken!

Sieht zwar irgendwie rund aus, aber wenn man genauer hinschaut, sieht man dass es nicht wirklich rund ist sondern ziemlich zackig.

Um eine weiche Kante zu erreichen, wird in ein separates Bitmap ein abgerundetes Rechteck mit Antialiasing gezeichnet, welches als Maske dient. Durch das Antialiasing in der Maske sind die Ecken weichgezeichnet.

contentWithCorners sieht nun in etwa so aus:

Maske

Es ist zwar nur schwarz drauf, aber die Ecken sehen schon mal viel runder aus.

Der eigentliche Inhalt wird per Alpha Blending über die Maske gezeichnet, was den Effekt hat, dass die Transparenzwerte der Maske beibehalten werden, die Farbwerte aber aus dem darüber gezeichneten Bild übernommen werden.

Zusammengesetzt sieht das so aus:

Mit runden Ecken

Dank Antialiasing sehen die Ecken auch schön rund aus. Das Ganze kann nun auf den Canvas gezeichnet werden:

In das RoundedFrameLayout kann man nun alles mögliche rein stecken und es ist sichergestellt, dass die Ecken rund sind.

 

Noch mehr Sonderwünsche? – Beispiel für eine eigene Implementation von View

Es kann natürlich auch vorkommen, dass man weder mit der Zusammensetzung von Views noch mit der Anpassung von bestehenden UI-Elementen zum Ziel Kommt. Dann kann man einfach alles selber machen, indem man View selbst implementiert. Damit hat mein ein leeres Canvas, worauf man alles zeichnen kann was man möchte.

Das sind in Hannibal Mobile vor allem die Diagramme, die von AChartEngine gezeichnet werden.

Das sind sowohl Diagramme in den Gauges:

Als auch die Detail-Diagramme:

Fazit

Wann sollte man welche der drei vorgestellen Methoden nehmen um Custom-Views zu implementieren?

Zusammensetzen von Views

  • Use Case: Man hat eine Gruppe von Views, die oft zusammen verwendet werden. Das ist die einfachste Möglichkeit eigene Views umzusetzen.
  • Beispiel: Zwei Buttons, Plus/Minus buttons, sollten immer zusammen und nebeneinander erscheinen.
  • Lösung: Von LinearLayout ableiten und im Konstruktor die zwei Buttons zum Layout hinzufügen. Ausserdem kann man im abgeleiteten Layout Methoden anbieten um Event Handler auf die einzelne Buttons zu registrieren (z.B. setOnPlusClickListener()).

Anpassen von UI-Elementen

  • Use Case: Ein bestehendes UI-Element macht ungefähr das was man will, aber braucht Anpassungen.
  • Beispiel: Ein Textfeld, das je abhängig vom Inhalt eine andere Hintergrundfarbe bekommt.
  • Lösung: Von EditText ableiten und per addTextChangedListener() einen Event Handler setzen, der den Hintergrund je nach Textinhalt anpasst.

Eigene Implementation von View

  • Use Case: Man kommt weder mit Zusammensetzung noch mit Anpassung zum Ziel. Diese Möglichkeit verursacht am meisten Arbeit, ist jedoch auch am flexibelsten.
  • Beispiel: Man braucht ein interaktives Diagramm.
  • Lösung: Von View ableiten und im onDraw() das Diagramm zeichnen und per onTouchEvent() die Benutzereingaben verarbeiten.

0 Kommentare

Einen Kommentar abschicken

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *