Wir entwickeln im Auftrag eines Kunden eine Mobile-Applikation, die ein reichhaltiges grafisches Frontend nutzt. Sie soll dem Nutzer das Angebot unseres Kunden schmackhaft machen. Das Design enthält viele gleichzeitig ablaufende Animationen. Wir entwickeln das Projekt iterativ. Dabei ist die Komplexität des Frontends eines der grösseren Risiken und wir haben uns früh entschlossen, die Umsetzung mittels Android View Framework auszuprobieren.
Nach einer Weile stellte sich heraus, dass das Android-Framework bei eingeschalteter Hardwarebeschleunigung einige wichtige Animationen nicht unterstützt. Namentlich waren dies in unserem Fall die Methode drawTextOnPath() und animierte Alpha-Werte. Zudem gibt es weitere Funktionen, die sich auf verschiedenen Devices bei eingeschalteter Hardwarebeschleunigung unterschiedlich verhalten.
class AlphaAnimation extends Animation { … @Override protected void applyTransformation(float interpolatedTime, Transformation t) { t.setAlpha(calculateNewAlpha(interpolatedTime)); } }
Dieser Code wird auf vielen Devices nicht korrekt animiert, wenn Hardwarebeschleunigung aktiviert ist. Weitere nicht (oder nur teilweise) unterstützte Methoden findet man unter folgender Adresse: http://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported
Die Hardwarebeschleunigung ist essentiell. Das Frontend läuft nur mit dieser schnell genug, um dem User das Gefühl von Unmittelbarkeit zu vermitteln, das der Kunde sucht. Wir hätten für diese Schwierigkeiten Workarounds und Hacks implementieren können, aber dieses Vorgehen ist auch mit einem Risiko behaftet. Schon während der Projektinitialisierung haben wir deshalb als Minderungsstrategie geplant die Implementation auch mit OpenGL zu versuchen. OpenGL wird oft in Games eingesetzt, die eine gewisse Grafik-Leistung erbringen müssen und die App hat einige Parallelen zu Games.
Wir nahmen uns also drei Tage Zeit, das Frontend prototypisch in OpenGL ES 1.1 zu testen. Wir haben uns entschieden 1.1 zu verwenden, da wir uns den zusätzlichen Code Overhead von 2.0 (1.1 Renderpipeline in Shader implementieren) ersparen wollten, und 1.1 genug Funktionalität liefert um die gewünschten Effekte zu erzielen. Da die Designs in 2D gehalten sind, reichen nämlich Sprites. Um uns die Arbeit leichter zu machen, haben wir entsprechende Klassen gebaut, die das Mapping der Grafiken als Textur auf ein Quad übernehmen. Glücklicherweise konnten wir schon recht viel von einem Toy Project, einem Geschicklichkeits-Spiel, übernehmen.
abtract class Sprite { … void render(GL10 gl) {…} void setAlpha(float alpha) {…} void setPos(PointF pos) {…} void setRotation(float rotation) {…} void setScale(float scale) {…} }
Nun können wir die dynamische View implementieren. Grafiken werden zu beginn automatisch in den Speicher geladen. Während dem Betrieb wird das ViewModel in einem eigenen Thread aktualisiert. Durch den Renderthread wird dann alles an der neuen Position gezeichnet. Dadurch sind Updates und das Zeichnen entkoppelt.
class Sprite { … public void render(GL10 gl) { // add transparency gl.glColor4f(1.0f, 1.0f, 1.0f, mAlpha); // move sprite gl.glTranslatef(mPos.x, mPos.y, 0); // rotate sprite gl.glRotatef(mRotation, 0, 0, 1); // scale sprite, keeping aspect ratio the same gl.glScalef(mScale, mScale, 1); // move sprite center to 0,0 gl.glTranslatef(-0.5f * getWidth(),-0.5f * getHeight(), 0); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, cTextureCoordsBuffer); gl.glVertexPointer(2, GL10.GL_FLOAT, 0, mVertexBuffer); gl.glDrawElements(GL10.GL_TRIANGLES, 6, GL10.GL_UNSIGNED_SHORT, cIndiceBuffer); } }
Selbstverständlich testen wir das Frontend während der Entwicklung auch auf sehr unterschiedlichen Devices (verschiedene Hersteller, verschiedene Auflösungen und verschiedene Screen-Dichten). Die App verwendet das Ressourcen-System von Android um die Grafik in der richtigen Grösse zu laden (http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources). Dies verkürzt den Lade-Prozess auf älteren Devices erheblich und die Animationen sind so auch auf älteren Devices noch flüssig.
Die OpenGL Implementation der View ist sehr performant. Sogar Besitzer von älteren Mobiles kommen so in den Genuss von flüssigen Animationen. Der Aufwand für den zusätzlichen OpenGL Prototypen hat sich somit mehr als gelohnt.