Android und iOS Apps erstelle ich gerne mit Xamarin.Forms. Wegen der plattformübergreifenden Code-Basis spare ich Zeit und kann mich auf weniger Programmiersprachen konzentrieren. Auf plattformspezifische Features muss ich im Forms Projekt entweder verzichten, oder diese in den Plattformprojekten umsetzen.
Für die Interaktion mit dem Benutzer stehen mir verschiedene Touch-Actions zur Verfügung (Gesture recognizer). Gesten wie Tap, Pinch, Pan, Swipe, Drag & Drop kann ich so einfach erkennen und bereits raffinierte Interaktionsmöglichkeiten umsetzen.
Manchmal benötige ich jedoch mehr Flexibilität und möchte wissen, wo genau sich einer oder mehrere Finger des Benutzers auf dem Bildschirm befinden. Des Weiteren möchte ich die Bewegungspfade der Finger nachvollziehen können. Dafür brauche ich direkten Zugriff auf die Touch-Actions der jeweiligen Plattform.
In diesem Blog möchte ich anhand einer Demo-Applikation zeigen, wie ich Touch-Tracking in Xamarin.Forms mit minimalem Einsatz von plattformabhängigem Code eingesetzt habe.
In Xamarin.Forms selbst gibt es keine Unterstützung zur Nachverfolgung einzelner Finger Pfade. Es gibt jedoch die Möglichkeit einer plattformspezifischen Implementierung von Touch-Actions. Der Vorteil dabei ist, dass Pfade einzelner Finger unabhängig voneinander erfasst und sogar Informationen zum Druckpunkt ausgelesen werden können.
In meiner Demo App FourierDrawing (Xamarin Android) kann auf dem Bildschirm mit dem Finger ein Pfad gezeichnet werden. Dieser wird anschliessend durch ein Taylerpolynom approximiert und durch eine definierbare Anzahl epizyklischer Kreise reproduziert. Im Folgenden möchte ich mich jedoch lediglich auf die Implementierung der Fingerpfad-Erkennung konzentrieren:
namespace FourierDrawing.Droid { public class TouchEffect : PlatformEffect { Android.Views.View view; Element formsElement; TouchAction.TouchEffect libTouchEffect; bool capture; Func<double, double> fromPixels; int[] twoIntArray = new int[2]; static Dictionary<Android.Views.View, TouchEffect> viewDictionary = new Dictionary<Android.Views.View, TouchEffect>(); static Dictionary<int, TouchEffect> idToEffectDictionary = new Dictionary<int, TouchEffect>(); protected override void OnAttached() { // Get the Android View corresponding to the Element that the effect is attached to view = Control ?? Container; // Get access to the TouchEffect class in the .NET Standard library TouchAction.TouchEffect touchEffect = (TouchAction.TouchEffect)Element.Effects. FirstOrDefault(e => e is TouchAction.TouchEffect); if (touchEffect != null && view != null) { viewDictionary.Add(view, this); formsElement = Element; libTouchEffect = touchEffect; // Save fromPixels function fromPixels = view.Context.FromPixels; // Set event handler on View view.Touch += OnTouch; } } //...
TouchEffect
ableitende Klasse TouchEffect.cs mit ihren Abhängigkeiten (TouchActionEventArgs.cs, TouchActionEventHandler.cs, TouchActionType.cs) hinzugefügt. Diese ermöglicht eine Verwendung im Forms Projekt.
namespace FourierDrawing.TouchAction { public class TouchEffect : RoutingEffect { public event TouchActionEventHandler TouchAction; public TouchEffect() : base("XamarinDocs.TouchEffect") { } public bool Capture { set; get; } public void OnTouchAction(Element element, TouchActionEventArgs args) { TouchAction?.Invoke(element, args); } } }
TouchEffect
folgendermassen verwendet werden:<ContentView VerticalOptions="FillAndExpand"> <skia:SKCanvasView x:Name="CanvasView" PaintSurface="OnCanvasViewPaintSurface" BackgroundColor="White"/> <ContentView.Effects> <touchAction:TouchEffect Capture="True" TouchAction="OnTouchEffectAction" /> </ContentView.Effects> </ContentView>
OnTouchEffectAction
und verwende die TouchActionEventArgs
. Bei Touch-Actions bekomme ich den Aktionstyp, die ID (es können mehrere Aktionen gleichzeitig stattfinden) und die Location mitgeliefert. Die Location kann ich einem bestehenden Pfad anhängen und diesen anschliessend auf dem Bildschirm darstellen
private void OnTouchEffectAction(object sender, TouchActionEventArgs args) { switch (args.Type) { case TouchActionType.Pressed: if (_inProgressPaths.Count != 0) return; if (!_inProgressPaths.ContainsKey(args.Id)) { var path = new SKPath(); path.MoveTo(ConvertToPixel(args.Location)); _inProgressPaths.Add(args.Id, path); } break; case TouchActionType.Moved: if (_inProgressPaths.ContainsKey(args.Id)) { var path = _inProgressPaths[args.Id]; path.LineTo(ConvertToPixel(args.Location)); } break; case TouchActionType.Released: if (_inProgressPaths.ContainsKey(args.Id)) { _completedPaths.Clear(); _inProgressPaths[args.Id].Close(); _completedPaths.Add(_inProgressPaths[args.Id]); _inProgressPaths.Remove(args.Id); _fixPath.Clear(); CreateFourierSerie(); } break; case TouchActionType.Cancelled: if (_inProgressPaths.ContainsKey(args.Id)) { _inProgressPaths.Remove(args.Id); } break; } }
Bei der Verwendung von Touch Gesten reicht mir die Flexibilität eines Gesture Recognizers in den meisten Fällen aus und ich mache kaum Gebrauch einer eigenen Implementierung. Die gezeigte Variante funktioniert jedoch sehr zuverlässig und ist auch mit mässigem Aufwand umsetzbar. Bei der Programmierung von Mobile-Apps hat Einfachheit jedoch meist Vorrang vor ausgefeilter Interaktion.
Nun wüsche ich viel Spass beim Erstellen eurer interaktiven Benutzeroberflächen!
Schreiben Sie einen Kommentar