Fragmentierung auf der Android-Plattform

Bildschirme grob und fein, groß und klein

Der offensichtlichste Unterschied zwischen allen Androiden ist ihr Äußeres. Kein Android-Modell gleicht dem anderen. Die Spanne reicht hier von kleinen Kompakthandys über iPhone-Klone bis hin zu tabletoiden Smartphones und richtigen Tablets. Und auf all diesen Geräten soll meine App ein gleich gutes Bild von sich geben? Seit der Android-Version 1.6 stehen dem Entwickler dazu glücklicherweise die notwendigen Board-Mittel zur Verfügung. Ist man mit den Grundkonzepten vertraut und beachtet die entsprechenden Empfehlungen der offiziellen Dokumentation, steht einer erfolgreichen Cross-Device-UI-Entwicklung nichts im Wege.

Bildschirme grob und fein

Betrachten wir zunächst das Problem der unterschiedlichen Auflösungen. Unterschiedliche Auflösungen bedeuten, dass grafische Elemente wie Bilder, die eine fixe Pixelgröße haben, bei hoher Auflösung kleiner dargestellt werden als bei niedriger. Grund dafür ist die Pixeldichte, die mit der Auflösung naturgemäß zunimmt. Die Bildverkleinerung bei hoher Auflösung ist ein unerwünschtes Phänomen. Man stelle sich z. B. einen mittels verschiedener Grafiken implementierten Button vor. Bei hoher Auflösung kann es hier vorkommen, dass dieser wegen seiner geringen Größe vom Benutzer nicht mehr clickbar ist.

Was wir also grundsätzlich wollen, ist Konstanz in der physischen Größe. Bildelemente sollen sich dem Benutzer stets in der gleichen Größe präsentieren. Definiert man die Größe der verwendeten grafischen Elemente nicht in klassischen Pixel (px), sondern korrekterweise in dp bzw. dip (device independent pixel), so erhält man genau dieses gewünschte Verhalten.

Dp bildet sozusagen eine elastische Größenangabe, die sich der jeweiligen Bildschirmauflösung anpasst. Während 100 dp bei einer Bildschirmauflösung von 160 dpi noch genau 100 px entsprechen, ändert sich dies bei anderen Auflösungen. Bei 120 dpi sind es beispielsweise nur noch 75 px, bei 240 dpi jedoch 150 px.

Für die Definition von Größenangaben in XML-Layoutfiles steht einem neben dps zusätzlich noch der Wert wrap_content zur Verfügung. ImageViews, die wrap_content für die Höhe und Breite verwenden, werden vom System automatisch auf ihre originale Größe skaliert.

Grundsätzlich spricht nichts dagegen, dp-Angaben auch für skalierbare Textgrößen zu verwenden. Allerdings gibt es hier noch eine bessere Option, die sog. sp (screen independent pixel). Sps verhalten sich wie dps, wobei zusätzlich noch die benutzerdefinierbare Systemschriftgröße berücksichtigt wird. Verwendet man also brav dp, sp und wrap_content, kümmert sich das System automatisch um die richtige Umrechnung und Skalierung.

Bedenkt man jedoch, dass hierbei ein großer Skalenbereich von Auflösungen abgedeckt werden muss, verwundert es nicht, dass auch das beste System bei großen Skalierungen ins Schwitzen kommt. Beim Hochskalieren kommt es naturgemäß zu einer Verschlechterung der Bildqualität, beim Runterskalieren können andere Artefakte auftreten.

Doch auch hier wurde von den Entwicklern der Plattform eine Lösung vorgesehen. Der Schlüssel liegt in der Bereitstellung von unterschiedlich skalierten Bildressourcen für die unterschiedlichen Auflösungsbereiche. Um die Anzahl der zu unterstützenden Auflösungen in einem überschaubaren Maß zu halten, werden vom System vier verschiedene Auflösungsklassen unterschieden: ldpi, mdpi, hdpi und xhdpi. Diese stehen für low, medium, high sowie extra high density. Der von den einzelnen Klassen jeweils abgedeckte Bereich ist in Abb. 3 ersichtlich.

Abb. 3: Auflösungsklassen der Android-Plattform

Allgemein werden die von der Applikation verwendeten Ressourcen im res-Verzeichnis abgelegt. Grafiken landen dabei in res/drawable.

Möchte man nun eine Bilddatei, hier image.png, in verschiedenen Auflösungen bereitstellen, so muss man diese lediglich in spezielle Ressourcenverzeichnisse ablegen: drawable-[ldpi|mdpi|hdpi|xhdpi] . Je nach unterstützter Auflösungsklasse verteilt man also unterschiedlich aufgelöste Versionen von image.png in drawable-ldpi, drawable-mdpi usw. Hätte unser image.png z. B. eine mdpi-Auflösung von 100 x 100 px, wäre es sinnvoll, eine Version mit 75 x 75 px in ldpi und eine mit 150 x 150 px in hdpi bereitzustellen. Die jeweiligen Skalierungsfaktoren der einzelnen Auflösungsklassen, die sich jeweils auf die mittlere Auflösung als Basis beziehen, sind in Abb. 4 dargestellt.

Abb. 4: Skalierungsfaktoren für die Auflösungklassen

In der Applikation können wir die unterschiedlichen Bilddateien, die zwar alle den gleichen Filenamen haben, aber in unterschiedlichen Verzeichnissen liegen, über einen einzelnen Ressourcen-Identifier ansprechen, in unserem Fall: R.drawable.image. Das System kümmert sich dann automatisch um die Auswahl der geeignetsten Datei. Von einem res/drawable-Ordner ohne weitere Qualifikation der Auflösung wird übrigens immer angenommen, dass er Bildressourcen für die mdpi-Klasse bereitstellt.

Bildressourcen werden grundsätzlich vom System vorskaliert und gecacht. Das erhöht die Performance beim Zeichnen, braucht aber auch zusätzlich Speicher, was bei vielen und v. a. großen Bilddateien problematisch werden kann. Bilddateien, die aus irgendwelchen Gründen nicht skaliert werden sollen, kann man in drawable-nodpi ablegen. Alternativ kann man die Skalierung auch systemweit oder auf Bitmap-Ebene ein- und abschalten, siehe entsprechende API-Dokumentation. Beim programmatischen Umgang mit Bildressourcen ist noch zu beachten, dass stets die skalierten Pixelwerte von den jeweiligen API-Methoden zurückgeliefert werden. Erzeugt man zusätzlich Bilder in-memory, sollte man wissen, dass für diese implizit eine mdpi-Auflösung zugrunde gelegt wird. Beim konkreten Zeichenvorgang werden diese Bitmaps automatisch skaliert. Auch dieses Verhalten lässt sich nach Bedarf konfigurieren.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.