Wywoływanie zdarzeń z efektów
Efekt może definiować i wywoływać zdarzenie, sygnalizując zmiany w bazowym widoku natywnym. W tym artykule przedstawiono sposób implementowania śledzenia palców wielodotykowego niskiego poziomu oraz generowania zdarzeń sygnalizujących aktywność dotykową.
Efekt opisany w tym artykule zapewnia dostęp do zdarzeń dotyku niskiego poziomu. Te zdarzenia niskiego poziomu nie są dostępne w istniejących GestureRecognizer
klasach, ale są one istotne dla niektórych typów aplikacji. Na przykład aplikacja do malowania palcami musi śledzić poszczególne palce podczas poruszania się na ekranie. Klawiatura muzyczna musi wykrywać naciśnięcia i zwalniania na poszczególnych klawiszach, a także palec szybujący od jednego klawisza do drugiego w glissando.
Efekt jest idealny do śledzenia palców wielodotykowych, ponieważ może być przymocowany do dowolnego Xamarin.Forms elementu.
Zdarzenia platformy Touch
Wszystkie systemy iOS, Android i platforma uniwersalna systemu Windows obejmują interfejs API niskiego poziomu, który umożliwia aplikacjom wykrywanie aktywności dotykowej. Wszystkie te platformy rozróżniają trzy podstawowe typy zdarzeń dotykowych:
- Naciśnięcie, gdy palec dotyka ekranu
- Przeniesione, gdy palec dotykający ekranu porusza się
- Zwalniane, gdy palec jest zwalniany z ekranu
W środowisku wielodotykowym wiele palców może jednocześnie dotknąć ekranu. Różne platformy zawierają numer identyfikacyjny (ID), którego aplikacje mogą używać do rozróżnienia między wieloma palcami.
W systemie iOS UIView
klasa definiuje trzy metody, które można zastąpić, TouchesBegan
, TouchesMoved
i TouchesEnded
odpowiadające tym trzem podstawowym zdarzeń. W artykule Multi-Touch Finger Tracking opisano sposób używania tych metod. Jednak program systemu iOS nie musi zastąpić klasy, która pochodzi z UIView
, aby używać tych metod. System iOS UIGestureRecognizer
definiuje również te same trzy metody i można dołączyć wystąpienie klasy pochodzącej z UIGestureRecognizer
dowolnego UIView
obiektu.
W systemie View
Android klasa definiuje przesłoniętą metodę o nazwie OnTouchEvent
, aby przetworzyć wszystkie działania dotykowe. Typ działania dotykowego jest definiowany przez elementy Down
członkowskie wyliczenia, , Move
PointerDown
, Up
i PointerUp
zgodnie z opisem w artykule Multi-Touch Finger Tracking. System Android View
definiuje również zdarzenie o nazwie Touch
, które umożliwia dołączanie programu obsługi zdarzeń do dowolnego View
obiektu.
W platforma uniwersalna systemu Windows (UWP) UIElement
klasa definiuje zdarzenia o nazwach PointerPressed
, PointerMoved
i PointerReleased
. Opisano je w artykule Handle Pointer Input (Dane wejściowe wskaźnika obsługi) w witrynie MSDN i dokumentacji interfejsu UIElement
API dla klasy .
Interfejs Pointer
API w platforma uniwersalna systemu Windows ma na celu ujednolicenie danych wejściowych myszy, dotyku i pióra. Z tego powodu zdarzenie jest wywoływane, gdy mysz przechodzi przez element nawet wtedy, PointerMoved
gdy przycisk myszy nie jest naciśnięty. Obiekt PointerRoutedEventArgs
, który towarzyszy tym zdarzeń, ma właściwość o nazwie, która ma właściwość o nazwie Pointer
IsInContact
, która wskazuje, czy naciśnięcie przycisku myszy lub palcem jest w kontakcie z ekranem.
Ponadto platforma UWP definiuje jeszcze dwa zdarzenia o nazwie PointerEntered
i PointerExited
. Wskazują one, kiedy mysz lub palec przenosi się z jednego elementu do drugiego. Rozważmy na przykład dwa sąsiadujące elementy o nazwach A i B. Oba elementy mają zainstalowane programy obsługi dla zdarzeń wskaźnika. Po naciśnięciu palca na PointerPressed
A zdarzenie jest wywoływane. Gdy palec porusza się, A wywołuje PointerMoved
zdarzenia. Jeśli palec przechodzi z A do B, A wywołuje PointerExited
zdarzenie, a B wywołuje PointerEntered
zdarzenie. Jeśli palec zostanie zwolniony, B wywołuje PointerReleased
zdarzenie.
Platformy iOS i Android różnią się od platformy UWP: widok, który najpierw pobiera wywołanie TouchesBegan
lub OnTouchEvent
gdy palec dotyka widoku, nadal uzyskuje wszystkie działania dotykowe, nawet jeśli palec przenosi się do różnych widoków. Platforma UWP może zachowywać się podobnie, jeśli aplikacja przechwytuje wskaźnik: W PointerEntered
procedurze obsługi zdarzeń wywołania CapturePointer
elementu, a następnie pobiera wszystkie działania dotykowe z tego palca.
Podejście platformy UWP okazuje się być bardzo przydatne w przypadku niektórych typów aplikacji, na przykład klawiatury muzycznej. Każdy klucz może obsługiwać zdarzenia dotykowe dla tego klucza i wykrywać, kiedy palec przesuwa się z jednego klucza do innego przy użyciu zdarzeń PointerEntered
i PointerExited
.
Z tego powodu efekt śledzenia dotykowego opisany w tym artykule implementuje podejście platformy UWP.
Interfejs API efektu śledzenia dotykowego
Przykład zawiera klasy (i wyliczenie), które implementują śledzenie dotykowe niskiego poziomu. Te typy należą do przestrzeni nazw TouchTracking
i zaczynają się od słowa Touch
. Projekt biblioteki TouchTrackingEffectDemos .NET Standard zawiera TouchActionType
wyliczenie typu zdarzeń dotykowych:
public enum TouchActionType
{
Entered,
Pressed,
Moved,
Released,
Exited,
Cancelled
}
Wszystkie platformy zawierają również zdarzenie wskazujące, że zdarzenie dotykowe zostało anulowane.
Klasa TouchEffect
w bibliotece .NET Standard pochodzi z RoutingEffect
i definiuje zdarzenie o nazwie i metodę o nazwie TouchAction
OnTouchAction
, która wywołuje TouchAction
zdarzenie:
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);
}
}
Zwróć również uwagę na Capture
właściwość . Aby przechwycić zdarzenia dotykowe, aplikacja musi ustawić tę właściwość na true
wartość przed zdarzeniem Pressed
. W przeciwnym razie zdarzenia dotykowe zachowują się jak te w platforma uniwersalna systemu Windows.
Klasa TouchActionEventArgs
w bibliotece .NET Standard zawiera wszystkie informacje, które towarzyszą każdemu zdarzeniu:
public class TouchActionEventArgs : EventArgs
{
public TouchActionEventArgs(long id, TouchActionType type, Point location, bool isInContact)
{
Id = id;
Type = type;
Location = location;
IsInContact = isInContact;
}
public long Id { private set; get; }
public TouchActionType Type { private set; get; }
public Point Location { private set; get; }
public bool IsInContact { private set; get; }
}
Aplikacja może używać Id
właściwości do śledzenia poszczególnych palców. Zwróć uwagę na IsInContact
właściwość . Ta właściwość jest zawsze true
w przypadku Pressed
zdarzeń i false
zdarzeń Released
. Jest również zawsze true
w przypadku Moved
zdarzeń w systemach iOS i Android. Właściwość IsInContact
może dotyczyć false
Moved
zdarzeń na platforma uniwersalna systemu Windows, gdy program jest uruchomiony na pulpicie, a wskaźnik myszy porusza się bez naciśnięcia przycisku.
Możesz użyć TouchEffect
klasy we własnych aplikacjach, dołączając plik w projekcie biblioteki .NET Standard rozwiązania i dodając wystąpienie do Effects
kolekcji dowolnego Xamarin.Forms elementu. Dołącz procedurę obsługi do zdarzenia w TouchAction
celu uzyskania zdarzeń dotykowych.
Do użycia TouchEffect
we własnej aplikacji potrzebne będą również implementacje platformy zawarte w rozwiązaniu TouchTrackingEffectDemos .
Implementacje efektu śledzenia dotykowego
Implementacje TouchEffect
systemów iOS, Android i UWP opisane poniżej zaczynają się od najprostszej implementacji (UWP) i kończącej się implementacją systemu iOS, ponieważ jest bardziej złożona strukturalnie niż inne.
Implementacja platformy UWP
Implementacja platformy UNIWERSALNEJ TouchEffect
systemu Windows jest najprostsza. Jak zwykle klasa pochodzi z PlatformEffect
klasy i zawiera dwa atrybuty zestawu:
[assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.UWP.TouchEffect), "TouchEffect")]
namespace TouchTracking.UWP
{
public class TouchEffect : PlatformEffect
{
...
}
}
Przesłonięcia OnAttached
zapisuje niektóre informacje jako pola i dołącza programy obsługi do wszystkich zdarzeń wskaźnika:
public class TouchEffect : PlatformEffect
{
FrameworkElement frameworkElement;
TouchTracking.TouchEffect effect;
Action<Element, TouchActionEventArgs> onTouchAction;
protected override void OnAttached()
{
// Get the Windows FrameworkElement corresponding to the Element that the effect is attached to
frameworkElement = Control == null ? Container : Control;
// Get access to the TouchEffect class in the .NET Standard library
effect = (TouchTracking.TouchEffect)Element.Effects.
FirstOrDefault(e => e is TouchTracking.TouchEffect);
if (effect != null && frameworkElement != null)
{
// Save the method to call on touch events
onTouchAction = effect.OnTouchAction;
// Set event handlers on FrameworkElement
frameworkElement.PointerEntered += OnPointerEntered;
frameworkElement.PointerPressed += OnPointerPressed;
frameworkElement.PointerMoved += OnPointerMoved;
frameworkElement.PointerReleased += OnPointerReleased;
frameworkElement.PointerExited += OnPointerExited;
frameworkElement.PointerCanceled += OnPointerCancelled;
}
}
...
}
Procedura OnPointerPressed
obsługi wywołuje zdarzenie efektu przez wywołanie onTouchAction
pola w metodzie CommonHandler
:
public class TouchEffect : PlatformEffect
{
...
void OnPointerPressed(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Pressed, args);
// Check setting of Capture property
if (effect.Capture)
{
(sender as FrameworkElement).CapturePointer(args.Pointer);
}
}
...
void CommonHandler(object sender, TouchActionType touchActionType, PointerRoutedEventArgs args)
{
PointerPoint pointerPoint = args.GetCurrentPoint(sender as UIElement);
Windows.Foundation.Point windowsPoint = pointerPoint.Position;
onTouchAction(Element, new TouchActionEventArgs(args.Pointer.PointerId,
touchActionType,
new Point(windowsPoint.X, windowsPoint.Y),
args.Pointer.IsInContact));
}
}
OnPointerPressed
Sprawdza również wartość Capture
właściwości w klasie effect w bibliotece .NET Standard i wywołuje CapturePointer
wartość , jeśli jest to true
.
Inne programy obsługi zdarzeń platformy UWP są jeszcze prostsze:
public class TouchEffect : PlatformEffect
{
...
void OnPointerEntered(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Entered, args);
}
...
}
Implementacja systemu Android
Implementacje systemów Android i iOS muszą być bardziej złożone, ponieważ muszą implementować Exited
zdarzenia i Entered
, gdy palec przechodzi od jednego elementu do drugiego. Obie implementacje mają podobną strukturę.
Klasa systemu Android TouchEffect
instaluje program obsługi dla Touch
zdarzenia:
view = Control == null ? Container : Control;
...
view.Touch += OnTouch;
Klasa definiuje również dwa słowniki statyczne:
public class TouchEffect : PlatformEffect
{
...
static Dictionary<Android.Views.View, TouchEffect> viewDictionary =
new Dictionary<Android.Views.View, TouchEffect>();
static Dictionary<int, TouchEffect> idToEffectDictionary =
new Dictionary<int, TouchEffect>();
...
Polecenie viewDictionary
pobiera nowy wpis za każdym razem, OnAttached
gdy jest wywoływane przesłonięcia:
viewDictionary.Add(view, this);
Wpis jest usuwany ze słownika w pliku OnDetached
. Każde wystąpienie elementu TouchEffect
jest skojarzone z określonym widokiem, do którego jest dołączony efekt. Słownik statyczny umożliwia każdemu TouchEffect
wystąpieniu wyliczanie wszystkich innych widoków i odpowiadających TouchEffect
im wystąpień. Jest to konieczne, aby umożliwić przesyłanie zdarzeń z jednego widoku do innego.
System Android przypisuje kod identyfikatora do zdarzeń dotykowych, które umożliwiają aplikacji śledzenie poszczególnych palców. Ten idToEffectDictionary
kod identyfikatora jest skojarzony z wystąpieniem TouchEffect
. Element jest dodawany do tego słownika Touch
, gdy program obsługi jest wywoływany dla naciśnięcia palca:
void OnTouch(object sender, Android.Views.View.TouchEventArgs args)
{
...
switch (args.Event.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
FireEvent(this, id, TouchActionType.Pressed, screenPointerCoords, true);
idToEffectDictionary.Add(id, this);
capture = libTouchEffect.Capture;
break;
Element jest usuwany z idToEffectDictionary
momentu zwolnienia palca z ekranu. Metoda FireEvent
po prostu gromadzi wszystkie informacje niezbędne do wywołania OnTouchAction
metody:
void FireEvent(TouchEffect touchEffect, int id, TouchActionType actionType, Point pointerLocation, bool isInContact)
{
// Get the method to call for firing events
Action<Element, TouchActionEventArgs> onTouchAction = touchEffect.libTouchEffect.OnTouchAction;
// Get the location of the pointer within the view
touchEffect.view.GetLocationOnScreen(twoIntArray);
double x = pointerLocation.X - twoIntArray[0];
double y = pointerLocation.Y - twoIntArray[1];
Point point = new Point(fromPixels(x), fromPixels(y));
// Call the method
onTouchAction(touchEffect.formsElement,
new TouchActionEventArgs(id, actionType, point, isInContact));
}
Wszystkie inne typy dotyku są przetwarzane na dwa różne sposoby: jeśli Capture
właściwość to true
, zdarzenie dotykowe jest dość prostym tłumaczeniem TouchEffect
informacji. Staje się to bardziej skomplikowane, gdy Capture
jest to false
spowodowane tym, że zdarzenia dotykowe mogą być musiały zostać przeniesione z jednego widoku do drugiego. Jest to odpowiedzialność za metodę CheckForBoundaryHop
, która jest wywoływana podczas zdarzeń przenoszenia. Ta metoda korzysta z obu słowników statycznych. Wylicza viewDictionary
on w celu określenia widoku, który jest obecnie dotykany palcem, i używa idToEffectDictionary
go do przechowywania bieżącego TouchEffect
wystąpienia (a tym samym bieżącego widoku) skojarzonego z określonym identyfikatorem:
void CheckForBoundaryHop(int id, Point pointerLocation)
{
TouchEffect touchEffectHit = null;
foreach (Android.Views.View view in viewDictionary.Keys)
{
// Get the view rectangle
try
{
view.GetLocationOnScreen(twoIntArray);
}
catch // System.ObjectDisposedException: Cannot access a disposed object.
{
continue;
}
Rectangle viewRect = new Rectangle(twoIntArray[0], twoIntArray[1], view.Width, view.Height);
if (viewRect.Contains(pointerLocation))
{
touchEffectHit = viewDictionary[view];
}
}
if (touchEffectHit != idToEffectDictionary[id])
{
if (idToEffectDictionary[id] != null)
{
FireEvent(idToEffectDictionary[id], id, TouchActionType.Exited, pointerLocation, true);
}
if (touchEffectHit != null)
{
FireEvent(touchEffectHit, id, TouchActionType.Entered, pointerLocation, true);
}
idToEffectDictionary[id] = touchEffectHit;
}
}
Jeśli nastąpiła zmiana w metodzie idToEffectDictionary
, metoda potencjalnie wywołuje FireEvent
Exited
metodę i Entered
przenieść z jednego widoku do innego. Jednak palec mógł zostać przeniesiony do obszaru zajmowanego przez widok bez dołączonego TouchEffect
obszaru lub z tego obszaru do widoku z dołączonym efektem.
Zwróć uwagę na blok try
i catch
po korzystaniu z widoku. Na stronie, która zostanie wyświetlona, a następnie przejdzie z powrotem do strony głównej, metoda nie zostanie wywołana, a elementy pozostaną w elemecie viewDictionary
, OnDetached
ale system Android uzna je za usunięte.
Implementacja systemu iOS
Implementacja systemu iOS jest podobna do implementacji systemu Android z tą różnicą, że klasa systemu iOS TouchEffect
musi utworzyć wystąpienie pochodnej klasy UIGestureRecognizer
. Jest to klasa w projekcie systemu iOS o nazwie TouchRecognizer
. Ta klasa obsługuje dwa słowniki statyczne, które przechowują TouchRecognizer
wystąpienia:
static Dictionary<UIView, TouchRecognizer> viewDictionary =
new Dictionary<UIView, TouchRecognizer>();
static Dictionary<long, TouchRecognizer> idToTouchDictionary =
new Dictionary<long, TouchRecognizer>();
Większość struktury tej TouchRecognizer
klasy jest podobna do klasy systemu Android TouchEffect
.
Ważne
Wiele widoków w programie UIKit
nie ma domyślnie włączonej obsługi dotykowej. Dotyk można włączyć, dodając view.UserInteractionEnabled = true;
do OnAttached
przesłonięcia w klasie w TouchEffect
projekcie systemu iOS. Powinno się to zdarzyć po otrzymaniu UIView
elementu odpowiadającego elementowi, do którego jest dołączony efekt.
Wprowadzenie efektu dotykowego do pracy
Przykładowy program zawiera pięć stron, które testuje efekt śledzenia dotykowego pod kątem typowych zadań.
Strona Przeciąganie w widoku BoxView umożliwia dodawanie BoxView
elementów do elementu AbsoluteLayout
, a następnie przeciąganie ich po ekranie. Plik XAML tworzy wystąpienie dwóch Button
widoków w celu dodania BoxView
elementów do elementu AbsoluteLayout
i wyczyszczenia elementu AbsoluteLayout
.
Metoda w pliku za pomocą kodu, który dodaje nowy BoxView
element do AbsoluteLayout
obiektu, dodaje TouchEffect
również obiekt do BoxView
obiektu i dołącza program obsługi zdarzeń do efektu:
void AddBoxViewToLayout()
{
BoxView boxView = new BoxView
{
WidthRequest = 100,
HeightRequest = 100,
Color = new Color(random.NextDouble(),
random.NextDouble(),
random.NextDouble())
};
TouchEffect touchEffect = new TouchEffect();
touchEffect.TouchAction += OnTouchEffectAction;
boxView.Effects.Add(touchEffect);
absoluteLayout.Children.Add(boxView);
}
Program TouchAction
obsługi zdarzeń przetwarza wszystkie zdarzenia dotykowe dla wszystkich BoxView
elementów, ale musi wykonać pewną ostrożność: nie może zezwalać na dwa palce na jednym BoxView
, ponieważ program implementuje przeciąganie, a dwa palce zakłócałyby siebie nawzajem. Z tego powodu strona definiuje klasę osadzoną dla każdego aktualnie śledzonego palca:
class DragInfo
{
public DragInfo(long id, Point pressPoint)
{
Id = id;
PressPoint = pressPoint;
}
public long Id { private set; get; }
public Point PressPoint { private set; get; }
}
Dictionary<BoxView, DragInfo> dragDictionary = new Dictionary<BoxView, DragInfo>();
Zawiera dragDictionary
wpis dla każdego BoxView
aktualnie przeciąganego.
Akcja dotykowa Pressed
dodaje element do tego słownika i Released
usuwa go. Logika Pressed
musi sprawdzić, czy w słowniku znajduje się już element .BoxView
Jeśli tak, element jest już przeciągany, BoxView
a nowe zdarzenie jest drugim palcem na tym samym BoxView
obiekcie . Moved
W przypadku akcji i Released
program obsługi zdarzeń musi sprawdzić, czy słownik ma dla tego BoxView
wpis i czy właściwość touch Id
dla tego przeciągniętego BoxView
jest zgodna z właściwością w wpisie słownika:
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
BoxView boxView = sender as BoxView;
switch (args.Type)
{
case TouchActionType.Pressed:
// Don't allow a second touch on an already touched BoxView
if (!dragDictionary.ContainsKey(boxView))
{
dragDictionary.Add(boxView, new DragInfo(args.Id, args.Location));
// Set Capture property to true
TouchEffect touchEffect = (TouchEffect)boxView.Effects.FirstOrDefault(e => e is TouchEffect);
touchEffect.Capture = true;
}
break;
case TouchActionType.Moved:
if (dragDictionary.ContainsKey(boxView) && dragDictionary[boxView].Id == args.Id)
{
Rectangle rect = AbsoluteLayout.GetLayoutBounds(boxView);
Point initialLocation = dragDictionary[boxView].PressPoint;
rect.X += args.Location.X - initialLocation.X;
rect.Y += args.Location.Y - initialLocation.Y;
AbsoluteLayout.SetLayoutBounds(boxView, rect);
}
break;
case TouchActionType.Released:
if (dragDictionary.ContainsKey(boxView) && dragDictionary[boxView].Id == args.Id)
{
dragDictionary.Remove(boxView);
}
break;
}
}
Logika Pressed
ustawia Capture
właściwość TouchEffect
obiektu na true
. Ma to wpływ na dostarczanie wszystkich kolejnych zdarzeń dla tego palca do tej samej procedury obsługi zdarzeń.
Logika Moved
przenosi BoxView
obiekt przez zmianę dołączonej LayoutBounds
właściwości. Location
Właściwość argumentów zdarzenia jest zawsze względna względem BoxView
przeciągania, a jeśli BoxView
obiekt jest przeciągany ze stałą szybkością, Location
właściwości kolejnych zdarzeń będą w przybliżeniu takie same. Jeśli na przykład palec naciska BoxView
w środku, Pressed
akcja przechowuje PressPoint
właściwość (50, 50), która pozostaje taka sama w przypadku kolejnych zdarzeń. BoxView
Jeśli element jest przeciągany po przekątnej z stałą szybkością, kolejne Location
właściwości podczas Moved
akcji mogą być wartościami (55, 55), w tym przypadku Moved
logika dodaje wartość 5 do pozycji poziomej i pionowej BoxView
obiektu . BoxView
Porusza się tak, aby jego środek był ponownie bezpośrednio pod palcem.
Można jednocześnie przenosić wiele BoxView
elementów za pomocą różnych palców.
Podklasowanie widoku
Często łatwiej jest elementowi Xamarin.Forms obsługiwać własne zdarzenia dotykowe. Przeciąganie strony Przeciąganie elementu BoxView działa tak samo jak na stronie Przeciąganie obiektu BoxView, ale elementy przeciągnięte przez użytkownika są wystąpieniami klasy pochodzącej DraggableBoxView
z BoxView
klasy :
class DraggableBoxView : BoxView
{
bool isBeingDragged;
long touchId;
Point pressPoint;
public DraggableBoxView()
{
TouchEffect touchEffect = new TouchEffect
{
Capture = true
};
touchEffect.TouchAction += OnTouchEffectAction;
Effects.Add(touchEffect);
}
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
if (!isBeingDragged)
{
isBeingDragged = true;
touchId = args.Id;
pressPoint = args.Location;
}
break;
case TouchActionType.Moved:
if (isBeingDragged && touchId == args.Id)
{
TranslationX += args.Location.X - pressPoint.X;
TranslationY += args.Location.Y - pressPoint.Y;
}
break;
case TouchActionType.Released:
if (isBeingDragged && touchId == args.Id)
{
isBeingDragged = false;
}
break;
}
}
}
Konstruktor tworzy i dołącza TouchEffect
właściwość , a następnie ustawia właściwość po pierwszym utworzeniu Capture
wystąpienia tego obiektu. Słownik nie jest wymagany, ponieważ sama klasa przechowuje isBeingDragged
wartości , pressPoint
i skojarzone touchId
z każdym palcem. Obsługa zmienia właściwości iTranslationY
, aby logika działała nawet wtedy, gdy element nadrzędny obiektu DraggableBoxView
nie jest elementem AbsoluteLayout
.TranslationX
Moved
Integracja z usługą SkiaSharp
Następne dwa pokazy wymagają grafiki, a w tym celu używają biblioteki SkiaSharp. Przed zapoznaniem się z tymi przykładami warto dowiedzieć się więcej na temat korzystania z biblioteki SkiaSharp Xamarin.Forms . Dwa pierwsze artykuły ("SkiaSharp Drawing Basics" i "SkiaSharp Lines and Paths") obejmują wszystkie potrzebne tutaj elementy.
Strona Rysunku wielokropka umożliwia rysowanie wielokropka, przesuwając palcem na ekranie. W zależności od tego, jak przesuwasz palec, możesz narysować wielokropek od lewej górnej do prawej dolnej lub z dowolnego innego rogu do przeciwnego rogu. Wielokropek jest rysowany z losowym kolorem i nieprzezroczystością.
Jeśli następnie dotkniesz jednego z wielokropka, możesz przeciągnąć go do innej lokalizacji. Wymaga to techniki znanej jako "testowanie trafień", która polega na wyszukiwaniu obiektu graficznego w określonym punkcie. Wielokropek SkiaSharp nie Xamarin.Forms są elementami, więc nie mogą wykonywać własnego TouchEffect
przetwarzania. Element TouchEffect
musi mieć zastosowanie do całego SKCanvasView
obiektu.
Plik EllipseDrawPage.xaml tworzy wystąpienie SKCanvasView
w jednej komórce Grid
. Obiekt TouchEffect
jest dołączony do tego Grid
obiektu :
<Grid x:Name="canvasViewGrid"
Grid.Row="1"
BackgroundColor="White">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
W systemach Android i platforma uniwersalna systemu Windows TouchEffect
można dołączyć je bezpośrednio do SKCanvasView
elementu , ale w systemie iOS, który nie działa. Zwróć uwagę, że właściwość jest ustawiona Capture
na true
.
Każdy wielokropek renderowany przez bibliotekę SkiaSharp jest reprezentowany przez obiekt typu EllipseDrawingFigure
:
class EllipseDrawingFigure
{
SKPoint pt1, pt2;
public EllipseDrawingFigure()
{
}
public SKColor Color { set; get; }
public SKPoint StartPoint
{
set
{
pt1 = value;
MakeRectangle();
}
}
public SKPoint EndPoint
{
set
{
pt2 = value;
MakeRectangle();
}
}
void MakeRectangle()
{
Rectangle = new SKRect(pt1.X, pt1.Y, pt2.X, pt2.Y).Standardized;
}
public SKRect Rectangle { set; get; }
// For dragging operations
public Point LastFingerLocation { set; get; }
// For the dragging hit-test
public bool IsInEllipse(SKPoint pt)
{
SKRect rect = Rectangle;
return (Math.Pow(pt.X - rect.MidX, 2) / Math.Pow(rect.Width / 2, 2) +
Math.Pow(pt.Y - rect.MidY, 2) / Math.Pow(rect.Height / 2, 2)) < 1;
}
}
Właściwości StartPoint
i EndPoint
są używane, gdy program przetwarza dane wejściowe dotyku; Rectangle
właściwość jest używana do rysowania wielokropka. Właściwość LastFingerLocation
wchodzi w grę, gdy wielokropek jest przeciągany, a IsInEllipse
metoda pomaga w testowaniu trafień. Metoda zwraca true
wartość , jeśli punkt znajduje się wewnątrz wielokropka.
Plik związany z kodem przechowuje trzy kolekcje:
Dictionary<long, EllipseDrawingFigure> inProgressFigures = new Dictionary<long, EllipseDrawingFigure>();
List<EllipseDrawingFigure> completedFigures = new List<EllipseDrawingFigure>();
Dictionary<long, EllipseDrawingFigure> draggingFigures = new Dictionary<long, EllipseDrawingFigure>();
Słownik draggingFigure
zawiera podzbiór kolekcji completedFigures
. Procedura obsługi zdarzeń SkiaSharp PaintSurface
po prostu renderuje obiekty w tych completedFigures
kolekcjach i inProgressFigures
:
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Fill
};
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKCanvas canvas = args.Surface.Canvas;
canvas.Clear();
foreach (EllipseDrawingFigure figure in completedFigures)
{
paint.Color = figure.Color;
canvas.DrawOval(figure.Rectangle, paint);
}
foreach (EllipseDrawingFigure figure in inProgressFigures.Values)
{
paint.Color = figure.Color;
canvas.DrawOval(figure.Rectangle, paint);
}
}
Najtrudniejszą częścią przetwarzania dotykowego jest obsługa Pressed
. W tym miejscu jest wykonywane testowanie trafień, ale jeśli kod wykryje wielokropek pod palcem użytkownika, wielokropek może być przeciągnięty tylko wtedy, gdy nie jest obecnie przeciągany przez inny palec. Jeśli nie ma wielokropka pod palcem użytkownika, kod rozpoczyna proces rysowania nowego wielokropka:
case TouchActionType.Pressed:
bool isDragOperation = false;
// Loop through the completed figures
foreach (EllipseDrawingFigure fig in completedFigures.Reverse<EllipseDrawingFigure>())
{
// Check if the finger is touching one of the ellipses
if (fig.IsInEllipse(ConvertToPixel(args.Location)))
{
// Tentatively assume this is a dragging operation
isDragOperation = true;
// Loop through all the figures currently being dragged
foreach (EllipseDrawingFigure draggedFigure in draggingFigures.Values)
{
// If there's a match, we'll need to dig deeper
if (fig == draggedFigure)
{
isDragOperation = false;
break;
}
}
if (isDragOperation)
{
fig.LastFingerLocation = args.Location;
draggingFigures.Add(args.Id, fig);
break;
}
}
}
if (isDragOperation)
{
// Move the dragged ellipse to the end of completedFigures so it's drawn on top
EllipseDrawingFigure fig = draggingFigures[args.Id];
completedFigures.Remove(fig);
completedFigures.Add(fig);
}
else // start making a new ellipse
{
// Random bytes for random color
byte[] buffer = new byte[4];
random.NextBytes(buffer);
EllipseDrawingFigure figure = new EllipseDrawingFigure
{
Color = new SKColor(buffer[0], buffer[1], buffer[2], buffer[3]),
StartPoint = ConvertToPixel(args.Location),
EndPoint = ConvertToPixel(args.Location)
};
inProgressFigures.Add(args.Id, figure);
}
canvasView.InvalidateSurface();
break;
Innym przykładem SkiaSharp jest strona Finger Paint . Możesz wybrać kolor pociągnięcia i szerokość pociągnięcia z dwóch Picker
widoków, a następnie narysować jeden lub więcej palców:
Ten przykład wymaga również oddzielnej klasy do reprezentowania każdej linii malowanej na ekranie:
class FingerPaintPolyline
{
public FingerPaintPolyline()
{
Path = new SKPath();
}
public SKPath Path { set; get; }
public Color StrokeColor { set; get; }
public float StrokeWidth { set; get; }
}
Obiekt jest używany do renderowania SKPath
każdego wiersza. Plik FingerPaint.xaml.cs przechowuje dwie kolekcje tych obiektów: jedną dla aktualnie rysowanych wielolinii, a drugą dla ukończonych wielolinii:
Dictionary<long, FingerPaintPolyline> inProgressPolylines = new Dictionary<long, FingerPaintPolyline>();
List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();
Przetwarzanie Pressed
tworzy nowy FingerPaintPolyline
obiekt , wywołuje MoveTo
obiekt path w celu przechowywania punktu początkowego i dodaje ten obiekt do słownika inProgressPolylines
. Przetwarzanie Moved
wywołuje LineTo
obiekt path z nową pozycją palca, a Released
przetwarzanie przenosi ukończoną wielolinię z inProgressPolylines
do completedPolylines
. Po raz kolejny rzeczywisty kod rysunku SkiaSharp jest stosunkowo prosty:
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round
};
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKCanvas canvas = args.Surface.Canvas;
canvas.Clear();
foreach (FingerPaintPolyline polyline in completedPolylines)
{
paint.Color = polyline.StrokeColor.ToSKColor();
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
foreach (FingerPaintPolyline polyline in inProgressPolylines.Values)
{
paint.Color = polyline.StrokeColor.ToSKColor();
paint.StrokeWidth = polyline.StrokeWidth;
canvas.DrawPath(polyline.Path, paint);
}
}
Śledzenie widoku do widoku dotykowego
We wszystkich poprzednich przykładach ustawiono Capture
właściwość TouchEffect
elementu na true
, w momencie utworzenia TouchEffect
lub wystąpienia Pressed
zdarzenia. Gwarantuje to, że ten sam element odbiera wszystkie zdarzenia skojarzone z palcem, który po raz pierwszy nacisnął widok. Ostatnia próbka nie jest ustawiona Capture
na true
. Powoduje to różne zachowanie, gdy palec w kontakcie z ekranem przenosi się z jednego elementu do drugiego. Element, z którego przesuwa się palec, odbiera zdarzenie z właściwością ustawioną Type
na , a drugi element odbiera zdarzenie z ustawieniem Type
TouchActionType.Entered
.TouchActionType.Exited
Ten typ przetwarzania dotykowego jest bardzo przydatny w przypadku klawiatury muzycznej. Klawisz powinien być w stanie wykryć, kiedy jest naciśnięty, ale także wtedy, gdy palec przesuwa się z jednego klawisza do drugiego.
Na stronie Klawiatura dyskretna zdefiniowano małe WhiteKey
klasy BlackKey
pochodzące z Key
klasy , które pochodzą z BoxView
klasy .
Klasa Key
jest gotowa do użycia w rzeczywistym programie muzycznym. Definiuje właściwości publiczne o nazwie IsPressed
i KeyNumber
, które mają być ustawione na kod klucza ustanowiony przez standard MIDI. Klasa Key
definiuje również zdarzenie o nazwie StatusChanged
, które jest wywoływane podczas IsPressed
zmiany właściwości.
Na każdym klawiszu dozwolone jest wiele palców. Z tego powodu Key
klasa utrzymuje List
numery identyfikatorów dotykowych wszystkich palców, które obecnie dotykają tego klucza:
List<long> ids = new List<long>();
Procedura TouchAction
obsługi zdarzeń dodaje identyfikator do ids
listy zarówno Pressed
dla typu zdarzenia, jak i Entered
typu, ale tylko wtedy, gdy IsInContact
właściwość jest true
dla Entered
zdarzenia. Identyfikator jest usuwany z List
zdarzenia lub Exited
:Released
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
AddToList(args.Id);
break;
case TouchActionType.Entered:
if (args.IsInContact)
{
AddToList(args.Id);
}
break;
case TouchActionType.Moved:
break;
case TouchActionType.Released:
case TouchActionType.Exited:
RemoveFromList(args.Id);
break;
}
}
Metody AddToList
i RemoveFromList
sprawdzają, czy List
element zmienił się między pustymi i niepustymi StatusChanged
, a jeśli tak, wywołuje zdarzenie.
Różne WhiteKey
elementy i BlackKey
są rozmieszczone w pliku XAML strony, który wygląda najlepiej, gdy telefon jest przechowywany w trybie poziomym:
Jeśli zamiatasz palcem przez klucze, zobaczysz niewielkie zmiany koloru, że zdarzenia dotykowe są przenoszone z jednego klucza do drugiego.
Podsumowanie
W tym artykule pokazano, jak wywoływać zdarzenia w efekcie oraz jak pisać i używać efektu implementujące przetwarzanie wielodotykowe niskiego poziomu.