Individualisierte Steuerelemente mit Xamarin.Forms

15. September 2015

Xamarin.Forms ist eine Technologie, um Benutzeroberflächen zu beschreiben, sodass sie auf mehreren Plattformen (Android, iOS, Windows) mit derselben Logik aber mit plattformspezifischem Äußeren dargestellt werden. Dazu bildet Xamarin.Forms die Schnittmenge der Steuerelemente ab, die auf allen Plattformen vorhanden sind. In der Praxis bestehen Anwendungen aber nicht nur aus solchen Steuerelementen, sondern sie sollen an individuelle Bedürfnisse angepasst werden, um eine individuelle User Experience (UX) zu gewährleisten. Angefangen mit einfachem Styling über Templates bis hin zu Eigenentwicklungen von Steuerelementen bieten sich im Rahmen von Xamarin.Forms hierfür verschiedene Lösungsansätze.

So stellt sich die Frage, wann ein einfaches Styling reicht und wann ein Template oder wann der aufwändige Weg zur Erstellung eines eigenen Steuerelementes gegangen werden muss? In diesem Artikel sollen die technischen Möglichkeiten aufgezeigt und Hinweise zur Auswahl der richtigen Möglichkeit auf Basis meiner Erfahrungen gegeben werden.

Der Weg, wie Xamarin Controls abbildet

Auf allen Plattformen sind native UI-Controls implementiert, die für diese Plattformen typisch sind. Xamarin adaptiert diese und bildet sie als managed Pendants ab, sodass sie von CLR-kompatiblem Code aus verwendet werden können. Da sie sich zwischen den Plattformen in ihrer Bestimmung und Funktionsweise decken, hat Xamarin nochmals eine weitere Schicht über diese Adaptionen gelegt, um sie einheitlich verwenden zu können. Somit war die Grundidee von Xamarin.Forms geboren.

Doch wie genau macht Xamarin.Forms das?

Für jedes Steuerelement gibt es je eine CLR-Klasse, die auf die native Implementierung verweist. Zudem gibt es je Steuerelementgruppe für ein Steuerelement, das auf allen Plattformen verfügbar ist (z. B. alle Textblöcke), nochmals eine Xamarin.Forms-Klasse, um die gemeinsamen Eigenschaften dieser Gruppe abzubilden. Dieser Sachverhalt wird von der nachfolgenden Abbildung veranschaulicht. Zudem dient je Plattform ein sogenannter Renderer dazu, die Adaption zwischen Xamarin.Forms-Klasse und dem Xamarin-Adapter herzustellen.

xamarin controls

 

Individuelle Gestaltung von Benutzeroberflächen

Zunächst lassen sich im Rahmen von Xamarin.Forms Anwendungen über die gemeinsam abgebildeten Eigenschaften beeinflussen und somit gestalten. So hat jedes Label üblicherweise eine Text-Eigenschaft, um den auszugebenden Text anzugeben, aber auch Eigenschaften, um Schriftart, -größe und -stil anzugeben und vieles mehr. Dabei wird ausschließlich das Xamarin.Forms-Steuerelement verwendet, da es plattformunabhängig den nativen Steuerelementen der jeweiligen Plattformen mitteilt, welche Aktionen durchzuführen sind. Für das Label-Steuerelement z. B. ist diese Beziehung mit einem Auszug der Eigenschaften in der nachfolgenden Abbildung dargestellt.

properties

 

Sollen jedoch Anpassungen vorgenommen werden, die nur eine bestimmte Plattform betreffen oder die (noch) nicht von Xamarin.Forms ermöglicht werden, müssen diese Anpassungen durch Erweiterung des plattformspezifischen Renderes vorgenommen werden (siehe folgende Abbildung). Der Renderer stellt dabei eine Art Vermittler zwischen der Xamarin.Forms Steuerelement-Repräsentation und dem nativen Steuerelement dar und kann diese folglich bidirektional beeinflussen. Somit sorgt er letztlich dafür, dass die vom Xamarin.Forms-Steuerelement abgebildeten Charakteristika auf die Charakteristika des nativen Steuerelements verweisen. Auf diese Weise können alle vorhandenen Eigenschaften und Funktionen eines nativen Steuerelements beeinflusst und genutzt werden und Änderungen dieser zur Laufzeit vermittelt werden. Dabei kann auf diese Weise die Xamarin.Forms-Repräsentation des Steuerelements erweitert werden, indem Eigenschaften, Ereignisse und Aktionen des nativen Steuerelements an diese erweiterte Repräsentation weitervermittelt werden. Somit lässt sich eine – auf die Möglichkeiten der nativen Steuerelemente beschränkte – Erweiterung und Individualisierung vornehmen, die zudem sehr effizient ist, da die verwendbaren Möglichkeiten bereits nativ umgesetzt sind. Wenn es nötig ist, kann sogar das gesamte native Steuerelement gegen ein anderes ausgetauscht werden.

renderer

Neben dem Beeinflussen von Eigenschaften und dem Erweitern von Steuerelementen bieten manche Xamarin.Forms-Steuerelemente die Möglichkeit, Inhalte anzuzeigen. Dabei werden sogenannte Cells eingesetzt, die zum einen auf Basis nativer Steuerelemente vorgefertigt sind, und zum anderen aber auch individuell gestaltet werden können. Insbesondere die ViewCell bietet die Möglichkeit, unter Nutzung von Xamarin.Forms-Steuerelementen eine eigene Gestaltung dieser Inhalte vorzunehmen. Im Folgenden findet sich ein einfaches Beispiel hierzu, wobei in einer ViewCell, die als DataTemplate verwendet werden soll, zwei Textausgaben unterschiedlicher Formatierungen untereinander dargestellt werden sollen:

   1:  <DataTemplate>
   2:      <ViewCell>
   3:          <ViewCell.View>
   4:              <StackLayout Orientation="Horizontal">
   5:                  <Label Text="{Binding Name}" />
   6:                  <Label Text="{Binding Details}" FontSize="Micro" />
   7:              </StackLayout>
   8:          </ViewCell.View>
   9:      </ViewCell>
  10:  </DataTemplate>

So ist eine individuelle Gestaltung möglich, und theoretisch könnten auf diese Weise beliebig komplexe Benutzeroberflächen erzeugt werden. Jedoch ist man hier auf die in Xamarin.Forms abgebildeten Möglichkeiten beschränkt und muss auf die Performanz der Anwendung achten, da solche Templates aufgrund der Adaptionskette eher als schwergewichtig zu betrachten sind. Ferner ist die Abstraktionsmöglichkeit bei direkter Verwendung von DataTemplates beschränkt.

Erstellen von eigenen Steuerelementen

Wenn die zuvor aufgezeigte Flexibilität aufgrund von Abstraktionsmöglichkeiten oder Performanz nicht ausreichend ist, besteht zu guter Letzt die Möglichkeit, ein eigenes Steuerelement umzusetzen. Dafür gibt es zwei Wege: Zum einen kann ein solches Steuerelement als Ableitung eines Xamarin.Forms-Steuerelements umgesetzt werden. Zum anderen kann ein solches Steuerelement plattformspezifisch je Plattform umgesetzt werden.

Setzt man ein Steuerelement als Ableitung eines Xamarin.Forms-Steuerelements um, bietet sich am ehesten ein Layout an. Ein solches Steuerelement stellt sich wie ein erweitertes Template dar, welches wiederum mit Xamarin.Forms-Steuerelementen aufgebaut werden kann. Jedoch können erweiterte/dynamische Verhaltensweisen umgesetzt werden, und ferner gemeinsame übergreifende Eigenschaften und Funktionen definiert werden.

Ein triviales Beispiel dafür ist ein ItemsControl, wie man es aus WPF kennt. In ähnlicher Weise ist ein solches Steuerelement bereits in Form des ListViews vorhanden. Da dies jedoch die Selektion zur Zielsetzung hat und nativ abgebildet wird, ist eine Modifikation, dass es wie ein ItemsControl funktioniert, aufwändig oder ggf. sogar nicht praktikabel.

Im Wesentlichen definiert sich ein solches ItemsControl als StackLayout, welches das Binden einer ItemsSource ermöglicht und somit automatisch mit deren Items befüllt wird. Diese Items werden in einem festen Template oder in itemspezifischen Templates, welche über einen TemplateSelector ausgewählt werden, dargestellt. Somit wird das StackLayout mit aus dem jeweiligen Template erzeugten Steuerelementen befüllt. Dieser Zusammenhang wird auch noch mal in der Abbildung veranschaulicht. Eine Beispielimplementierung für ein solches ItemsControl findet sich hier: [.zip – 292KB]

Itemscontrol

Eine solche Umsetzung bietet den Charme, dass sie nur einmal für alle Plattformen implementiert werden muss. Allerdings birgt dieses Vorgehen leider auch Probleme: In Hinsicht auf die Performanz weist das Beispiel einen Engpass bei der dynamischen Befüllung auf. Der dynamische Anteil ist deshalb nur sehr beschränkt – in diesem Fall bis zu 20 Elementen – verwendbar. In nativen Controls dieser Art sind dafür in der Regel zur Performanz-Optimierung ein Control-Lifecycle und Virtualisierung implementiert. Dies müsste hier nachimplementiert werden, da das zu Grunde liegende Steuerelement dies nicht unterstützt. Somit entsteht auch hier wieder ein Aufwand, um die entstandenen Performanzeinbußen z. B. bei Aktualisierungen, auszugleichen.

Ferner stehen für den Inhalt nur nach Xamarin.Forms adaptierte Steuerelemente zur Verfügung, was die Flexibilität dieser Art und Weise, Controls zu erstellen, einschränkt. Und auch dabei ist zu bedenken, dass jedes von Xamarin.Forms erzeugte Steuerelement die vollständige Adaptionskette abbildet und zur Laufzeit im Speicher hält, und somit die Performanz negativ beeinflusst.

Um also ein Steuerelement umzusetzen, welches stets die optimale Plattformintegration und dazu noch eine optimale Performanz bietet, muss je Plattform ein natives Steuerelement umgesetzt werden (Beispiel für iOS), welches dann in Xamarin.Forms adaptiert (Beispiel für Android) werden muss. Dabei muss auf gemeinsame Eigenschaften und Verhaltensweisen über die verschiedenen Plattformen geachtet werden. Somit ist dies der aufwändigste Weg, ein eigenes Steuerelement umzusetzen, jedoch ist es auch der Weg mit den meisten Möglichkeiten und einem optimalen Ergebnis.

Fazit

Zuvor wurden verschiedene Möglichkeiten von einfach und schnell bis komplex und aufwändig aufgezeigt, wie mit Xamarin.Forms die UI einer Anwendung angepasst werden kann. Dabei ist klar geworden, dass jede Möglichkeit unterschiedliche Freiheitsgrade bietet aber auch ihre Grenzen hat. Um diese zu überwinden, gibt es jedoch immer eine Möglichkeit, die gewünschten Anforderungen umzusetzen:

Wenn Xamarin.Forms-Steuerelemente zu begrenzt sind, ist dem mit Renderern beizukommen. Wenn Templates zu einfach sind, können plattformunabhängige Steuerelemente umgesetzt werden. Wenn diese wiederum zu unperformant sind, können die Steuerelemente nativ umgesetzt werden.

Letztlich stellt sich in der Praxis natürlich die Frage, in welchem Umfang solche Lösungsansätze in Zeit und Budget umsetzbar sind. Meines Erachtens ist der Aufwand bis hin zu plattformunabhängigen Steuerelementen zu vertreten, da diese der erweiterte Arm einer View und schnell umsetzbar sind. Ist jedoch ein optimiertes Steuerelement erforderlich, würde ich raten, zu entsprechenden Bibliotheken von Anbietern wie Syncfusion, Telerik, ComponentOne, Infragistics oder Anderen zu greifen, sofern die Möglichkeit besteht.