• Noser.com
facebook
linkedin
twitter
youtube
  • NOSERmobile
    • Android
    • HTML 5
    • Hybrid Apps
    • iOS
    • Windows Phone
  • NOSERembedded
    • Medizintechnik
  • NOSERprojektmanagement
  • NOSERtesting
  • NOSERlifecycle
    • .NET Allgemein
    • Application Lifecylce Management
    • Apps
    • Architektur
    • ASP.NET
    • Azure
    • Cleancode
    • Cloud
    • Silverlight
    • Visual Studio / Team Foundation Server
    • Windows 8
    • Windows Presentation Foundation
  • NOSERinnovation
    • Big Data
    • Cloud
    • IoT
    • Operations Research
    • Augmented Reality
    • RFID, NFC, Bluetooth LE

Key-Frame Animationen mit ConstraintLayout & ConstraintSet erstellen

22. Mai 2018
Rehmann Christoph
1
ConstraintLayout, ConstraintSet, Key-Frame Animation, UX

Eine gute mobile App muss nicht nur zuverlässig funktionieren, sondern auch ein ansprechendes UI-Design und eine gute Benutzererfahrung bieten. Animationen können bei richtiger Verwendung dazu beitragen, die Aufmerksamkeit der Benutzer auf sich zu ziehen und die App von anderen abzuheben. Mit dem ConstraintLayout von Android lassen sich dabei auf sehr einfache Art und Weise kommplexe Animationen erstellen.

ConstraintLayout

Das ConstraintLayout wurde erstmals vor zwei Jahren von Google vorgestellt und hat seit rund einem Jahr die Beta-Phase hinter sich gelassen. Es ist vergleichbar mit dem RelativeLayout, bietet jedoch mehr Flexibilität. Damit lassen sich komplexe Layout Hierarchien in einer flachen Struktur abbilden. Dadurch wird die Zeit für das Rendern eines Layouts reduziert und eine bessere Performance erreicht.

Man kann es aber auch in Kombination mit ConstraintSet verwenden, um komplexe Animationen auf einfache Art und Weise umzusetzen. Die Grundidee ist, dass man ein Start-Layout und ein End-Layout (sogenannte Key-Frames) definiert. Anschliessend animiert der TransitionManager den Übergang vom ConstraintSet des Start-Layouts zum ConstraintSet des End-Layouts.

Das fertige Beispiel sieht wie folgt aus:

Vorschau der Animation

Walkthrough

Start-Layout erstellen

Als erstes erstellen wir das initiale Layout. Das Beispiel ist relativ simpel aufgebaut. Der Titel und Untertitel sind Links ausgerichtet und mit einem Margin von Oben versetzt. Der grösste Teil des Bildschirms wird von dem Bild eingenommen, welches oberhalb der Beschreibung platziert ist.

Start-LayoutZiel-Layout  erstellen

Beim Ziel-Layout sind die Texte ausgeblendet und das Bild nimmt den ganzen Platz ein. Der rechte Rand des Titels ist am linken Rand des Layouts ausgerichtet. Sprich wurde nach Links herausgeschoben. Die Beschreibung wurde ebenfalls nicht einfach ausgeblendet, sondern an den unteren Rand des Layouts ausgerichtet.

ConstraintSet erstellen

Nachdem wir nun beide Layouts erstellt haben, können wir das ConstraintSet für das Ziel-Layout erstellen. Dies kann mit nur zwei Zeilen Code gemacht werden:

val endConstraintSet = ConstraintSet()
endConstraintSet.clone(this, R.layout.activity_monkey_detail)

Das ConstraintSet beinhaltet lediglich die Constraints, Grössen und Margins des Layouts. Andere Attribute werden nicht beachtet und könnten demnach auch weggelassen und müssen somit nicht jeweils abgeglichen werden.

ConstraintSet auf Layout anwenden

Wollen wir die Animation nun ausführen, so müssen wir nur eine Transition mit dem TransitionManager starten und das ConstraintSet auf das Layout anwenden:

TransitionManager.beginDelayedTransition(constraintLayout)
endConstraintSet.applyTo(constraintLayout)

Mit diesen vier Zeilen Code haben wir bereits die ganze Animation implementiert.

Standardmässig wird die Animation linear interpoliert. Dies kann man jedoch sehr einfach anpassen, indem man einen anderen Interpolator verwendet. In meiner Demo-App habe ich z.B. FastOutSlowInInterpolator verwendet:

val transition = ChangeBounds()
transition.interpolator = FastOutSlowInInterpolator()
TransitionManager.beginDelayedTransition(constraintLayout, transition)

Zusammenfassung

Mit Hilfe von Key-Frame Animationen kann sehr einfach eine komplexe Animation implementieren. Man muss dazu nur das Start- und End-ConstraintSet definieren und lässt den TransitionManager die Arbeit machen. Bei einfacheren Animation muss man sich überlegen, ob man wirklich zwei Layouts anlegen will. Hier kann es allenfalls sinnvoller sein, die Animation im Code abzubilden.

ReactiveUI und Xamarin.Forms – Ein Beispiel

12. Dezember 2017
Rehmann Christoph
0
Reactive Programming, Rx, Xamarin, Xamarin.Forms
Banner Xamarin und ReactiveUI

ReactiveUI ist ein cross-platform Mvvm-Framework. Wie der Name vermuten lässt, setzt ReactiveUI auf den Reactive Extensions (Rx) auf. Es bietet neben WPF und UWP auch eine erstklassige Unterstützung für Xamarin Native und natürlich Xamarin Forms. Desweiteren ist es Open-Source und wurde mittlerweile in die .NET Foundation aufgenommen.

Beispiel Projekt

Anhand folgendem Beispiel möchte ich zeige, wie mit ReactiveUI und Xamarin.Forms eine Live-Suche implementiert werden kann. Die App ist einfach gestaltet. Man kann per Eingabefeld nach Ortschaften bzw. ÖV Stationen suchen. Die Suchanfragen werden jeweils an eine REST-Schnittstelle von opendata.ch abgesetzt. Anschliessend werden die Resultate in Form einer Liste dargestellt. Wer den kompletten Source Code einsehen will, findet diesen auf Github.

Anforderungen

  • Die App soll aus nur einer Page bestehen und folgende Elemente beinhalten:
    • Suchfeld,
    • Button
    • Liste für das Anzeigen von Suchresulateten
  • Eine ProgressBar zeigt an, wenn eine Suche am Laufen ist.
  • Der Such-Button soll deaktiviert sein, falls eine Suche am Laufen ist oder wenn das Suchfeld leer ist.
  • Sobald der Benutzer den Button betätigt oder den Text im Suchfeld ändert (Live Suche), soll eine Suchanfrage abgesetzt werden.

Damit der Server jedoch nicht mit unnötigen Suchanfragen bombadiert wird, soll die Anzahl der Aufrufe begrenzt werden. Wenn der Benutzer schnell tippt, macht es wenig Sinn nach jedem Buchstaben sofort eine neue Suche abzusetzen. Die Suche soll erst starten, wenn man für eine bestimmte Zeit keine weitere Eingabe gemacht wird. Wählt man einen kurzen Grenzwert, so fallt dies dem Benutzer auch nicht negativ auf. In diesem Beispiel wurden 500ms verwendet:

live search demo

Der Aufbau

Die Solution ist in drei Projekte aufgeteilt. Jeweils ein Plattformprojekt (iOS/Android) sowie ein Projekt mit einer .Net Standard Klassenbibliothek. In dieser befindet sich die Xamarin.Forms Teil mit View und ViewModel. Es kann somit die komplette Logik zwischen den beiden Apps geteilt werden.

Das ViewModel

Das SearchViewModel beinhaltet alle relevanten Properties. Weiter findet man darin auch die Logik für die Suche. Die basis ViewModel Implementation bei ReactiveUI heisst ReactiveObject. Diese Klasse implementiert bereits INotifyPropertyChanged. Somit kann man Properties direkt wie folgt definieren:

public string SearchQuery
{
	get { return _searchQuery; }
	set { this.RaiseAndSetIfChanged(ref _searchQuery, value); }
}

public ReactiveCommand<string, List<Station>> Search
{
	get { return _searchCommand; }
	private set { this.RaiseAndSetIfChanged(ref _searchCommand, value); }
}

Im Konstruktor des ViewModels werden die einzelnen Properties initialisiert. Für den Search Command kann man die statische Hilfsmethode CreateFromTask von ReactiveUI verwenden:

  Search = ReactiveCommand.CreateFromTask<string, List<Station>>(SearchAsync, CanSearch());

Der erste Parameter ist dabei die Methode, welche einen Suchtext entgegennimmt und eine Liste von Stationen zurück liefert. Beim zweiten Parameter handelt es sich um ein IObservable<bool>. Dieses Observable definiert, ob der Command ausgeführt werden darf oder nicht. Dabei wird auf folgende Bedingungen geachtet:

  • SearchQuery darf nicht leer sein
  • Die Suche darf nicht bereits ausgeführt werden.

Mit Rx ausgedrückt, kann dies wie folgt aussehen:

Observable.CombineLatest(
         this.WhenAnyValue(vm => vm.SearchQuery)
             .Select(searchQuery => !string.IsNullOrEmpty(searchQuery))
             .DistinctUntilChanged(),
         this.WhenAnyObservable(x => x.Search.IsExecuting)
             .DistinctUntilChanged(),
         (hasSearchQuery, isExecuting) => hasSearchQuery && !isExecuting)
     .Do(cps => System.Diagnostics.Debug.WriteLine($"Can Perform Search: {cps}"))
     .DistinctUntilChanged()

CombineLatest führt dabei zwei Observables so zusammen, dass jeweils immer nur der letzte Wert berücksichtigt wird. Der dritte Parameter ist eine Funktion, welche die beiden letzten Werte zu einem einzigen Boolean zusammenführt.

Live Suche (Throttling)

Die Live Suche kann man ebenfalls relativ einfach mit Rx und den Hilfsmethoden von ReactiveUI implementieren. Mit Hilfe der Throttle Funktion können schnell aufeinanderfolgende Eingaben ausgefiltert werden. Dies lässt sich gut mit einem sogenannten Marble Diagramm erklären.

Marble Diagramm für Throttle/Debounce

Die obere Zeitachse stellt den Input Stream dar und die untere Zeitachse den Output Stream. Die Implementation im ViewModel sieht dann wie folg aus:

// erstellt ein IObservable<string> von SearchQuery
this.WhenAnyValue(x => x.SearchQuery)
    // drosselt Änderungen von SearchQuery, sodas diese erst weitergereicht warden,
    // wenn für 500ms keine weiteren Änderungen passieren
    .Throttle(TimeSpan.FromMilliseconds(500), TaskPoolScheduler.Default)
    // Callback soll auf dem UI Thread stattfinden
    .ObserveOn(RxApp.MainThreadScheduler)
    // SearchCommand soll ausgeführt warden (sofern CanExecute == true)
    .InvokeCommand(Search)

An diesem Beispiel sieht man gut, wie ein komplexer Ablauf mit Hilfe von Rx auf einfache Weise umsätzen lässt. Würde man die gleiche Funktionalität in einem imperativen Still implementieren wollen, müsste man zusätzliche Zustands-Variablen und einen Timer verwenden. Rx erlaut jedoch eine deklartive Schreibweise. Diese führt meiner Meinung nach zu verständlicherem Code.

Die View

Nachdem wir nun das ViewModel implementiert haben, muss noch die View umgesetzt werden. Diese kann man entweder mit XAML oder per Code aufbauen. Bindings werden bei ReactiveUI jedoch bewusst im Code-Behind als Expressions geschrieben:

private void InitializeBindings()
{
    // Search Query
    this.Bind(ViewModel, x => x.SearchQuery, c => c.SearchEntry.Text)
        .DisposeWith(_bindingsDisposable);

    // Search Command
    this.BindCommand(ViewModel, x => x.Search, c => c.SearchButton, vm => vm.SearchQuery)
        .DisposeWith(_bindingsDisposable);

    // Activity Indicator
    this.WhenAnyObservable(x => x.ViewModel.Search.IsExecuting)
        .BindTo(ActivityIndicator, c => c.IsRunning)
        .DisposeWith(_bindingsDisposable);

    // Results
    this.OneWayBind(ViewModel, x => x.SearchResults, c => c.SearchResults.ItemsSource)
        .DisposeWith(_bindingsDisposable);
}

Das Command-Binding führt dabei nicht nur den Command bei einem Klick aus sonder kümmer sich auch darum den Button zu aktiviren und deaktivieren.

Fazit

Bisher hatte ich zwar noch nicht die Gelegenheit ReactiveUI in grösseren Projekten einzusetzen. Tortzdem macht das Framework für mich auf den ersten Blick einen guten Eindruck. Es bringt alle Werkzeuge mit, welche ich von einem Mvvm-Framework erwarte. Darüberhinaus liefert es viele nütziche Extensions. Diese erlauben es Bindings und Commands mit Rx zu verknüpfen.

Wer allerdings noch nie mit Rx gearbeitet hat, wird zu beginn einen etwas schweren Einstieg habe, da ReactiveUI sehr stark darauf aufbaut. In diesem Fall sollte man nebst der Dokumentation auch unbedingt die Beispiele genau anschauen.

1234567

Tag Cloud

.NET android Angular AngularJs app Arduino ASP.Net automated testing Azure Big Data C# C++ Cloud continuous integration Elm Embedded gRPC Internet of Things IoT Java Javascript M2M OWASP Projektmanagement protobuf Python Raspberry Pi Reactive Programming REST Scrum Security Softwarequalität SPA Testen testing Testmanagement Teststrategie Visual Studio WebAPI windows WPF Xamarin Xamarin.Android Xamarin.Forms Xamarin.iOS

Archive

Current Posts

  • Virtual pty/tty uart listener: Pitfalls on linux socat
  • Akzente setzen mit der Android Splash Screen API unter .NET MAUI
  • Do You have Your Personal Space?
  • Automated provisioning with ARM Templates
  • Asynchrone Beobachtungen und Versprechungen in Angular

Last Comments

  • Hans Reinsch bei Der Safety-Plan: Die wichtigsten Antworten mit Checkliste
  • George H. Barbehenn bei Modeling Optocouplers with Spice
  • Noser Blog Touch-Actions in Xamarin.Forms - Noser Blog bei Mach mehr aus Animationen in Xamarin.Forms mit SkiaSharp
  • Noser Blog Focus on the Secure Storage service of Trusted Firmware (TFM) - Noser Blog bei First run of the Trusted Firmware (TFM) application
  • Noser Blog First run of the Trusted Firmware (TFM) application - Noser Blog bei Focus on the Secure Storage service of Trusted Firmware (TFM)

Popular Posts

Xamarin.Android Code Obfuscation

6 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 1 - Aufbruch

5 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 2 - Kein Picknick

4 Comments

Contact us

  1. Name *
    * Please enter your name
  2. Email *
    * Please enter a valid email address
  3. Message *
    * Please enter message
© 2013 NOSER ENGINEERING AG. All rights reserved. Datenschutz | Cookie-Richtlinie