Vyvolání událostí z efektů

Download Sample Stažení ukázky

Efekt může definovat a vyvolat událost a signalizovat změny v podkladovém nativním zobrazení. Tento článek ukazuje, jak implementovat sledování vícedotykového prstu nízké úrovně a jak generovat události, které signalizují aktivitu dotykového ovládání.

Efekt popsaný v tomto článku poskytuje přístup k událostem dotykového ovládání nízké úrovně. Tyto události nízké úrovně nejsou dostupné prostřednictvím existujících GestureRecognizer tříd, ale jsou nezbytné pro některé typy aplikací. Například aplikace malování prstem musí sledovat jednotlivé prsty při pohybu na obrazovce. Hudební klávesnice potřebuje rozpoznat klepnutí a uvolnění jednotlivých kláves a také pohyb prstem z jedné klávesy do druhé v glissando.

Efekt je ideální pro sledování prstu s více dotyky, protože může být připojen k libovolnému Xamarin.Forms prvku.

Události dotykového ovládání platformy

Všechny systémy iOS, Android a Univerzální platforma Windows obsahují rozhraní API nízké úrovně, které umožňuje aplikacím detekovat dotykovou aktivitu. Všechny tyto platformy rozlišují mezi třemi základními typy dotykových událostí:

  • Stisknuto, když se prst dotkne obrazovky
  • Přesunuto, když se prstem dotknete obrazovky
  • Vydáno, když je prst uvolněn z obrazovky

Ve vícedotykovém prostředí se několik prstů může současně dotýkat obrazovky. Mezi různé platformy patří identifikační číslo (ID), které můžou aplikace použít k rozlišení mezi více prsty.

V iOSu UIView třída definuje tři přepisovatelné metody , TouchesBeganTouchesMoveda TouchesEnded odpovídající těmto třem základním událostem. Článek Sledování vícedotykových prstů popisuje, jak tyto metody používat. Program pro iOS však nemusí přepsat třídu, která je odvozena od UIView použití těchto metod. IOS UIGestureRecognizer také definuje stejné tři metody a můžete připojit instanci třídy, která je odvozena od UIGestureRecognizer jakéhokoli UIView objektu.

V Androidu View třída definuje přepisovatelnou metodu pojmenovanou OnTouchEvent ke zpracování všech aktivit dotykového ovládání. Typ dotykové aktivity je definován pomocí členů Downvýčtu , PointerDown, Move, Upa PointerUp jak je popsáno v článku Sledování vícedotykového prstu. Android View také definuje událost s názvem Touch , která umožňuje, aby obslužná rutina události byla připojena k libovolnému View objektu.

V Univerzální platforma Windows (UPW) UIElement třída definuje události s názvem PointerPressed, PointerMoveda PointerReleased. Jsou popsány v článku Zpracování vstupu ukazatele na WEBU MSDN a dokumentaci k rozhraní API pro UIElement třídu.

Rozhraní Pointer API v Univerzální platforma Windows je určeno ke sjednocení vstupu myši, dotykového ovládání a pera. Z tohoto důvodu je událost vyvolána, PointerMoved když se myš pohybuje přes prvek, i když tlačítko myši není stisknuto. Objekt PointerRoutedEventArgs , který doprovází tyto události, má vlastnost s názvem Pointer , která má vlastnost s názvem IsInContact , která označuje, zda je tlačítko myši stisknuto nebo prst je v kontaktu s obrazovkou.

Kromě toho UPW definuje dvě další události s názvem PointerEntered a PointerExited. Označují, kdy se myš nebo prst přesune z jednoho prvku do druhého. Představte si například dva sousední prvky s názvem A a B. Oba prvky mají nainstalované obslužné rutiny pro události ukazatele. Když prst stiskne na A, PointerPressed vyvolá se událost. Při pohybu prstem vyvolá PointerMoved A události. Pokud se prst přesune z A na B, vyvolá PointerExited událost a B vyvolá PointerEntered událost. Pokud se pak prst uvolní, vyvolá PointerReleased B událost.

Platformy pro iOS a Android se liší od UPW: Zobrazení, na které se poprvé zavolá TouchesBegan , nebo OnTouchEvent když se prst do zobrazení dotkne, bude i nadále dostávat všechny dotykové aktivity, i když se prst přesune do různých zobrazení. UPW se může chovat podobně, pokud aplikace zaznamená ukazatel: V PointerEntered obslužné rutině události volá CapturePointer prvek a pak získá veškerou dotykovou aktivitu z prstu.

Přístup pro UPW ukazuje, že je velmi užitečný pro některé typy aplikací, jako je například hudební klávesnice. Každá klávesa dokáže zpracovat události dotykového ovládání pro danou klávesu a zjistit, kdy se prst vysune z jednoho klíče do druhého pomocí PointerEntered událostí a PointerExited událostí.

Z tohoto důvodu efekt dotykového sledování popsaný v tomto článku implementuje přístup UPW.

Rozhraní API efektu dotykového sledování

Ukázka efektu touch tracking obsahuje třídy (a výčet), které implementují dotykové sledování nízké úrovně. Tyto typy patří do oboru názvů TouchTracking a začínají slovem Touch. Projekt knihovny TouchTrackingEffectDemos .NET Standard obsahuje TouchActionType výčet pro typ dotykových událostí:

public enum TouchActionType
{
    Entered,
    Pressed,
    Moved,
    Released,
    Exited,
    Cancelled
}

Všechny platformy také obsahují událost, která značí, že byla událost dotykového ovládání zrušena.

Třída TouchEffect v knihovně .NET Standard je odvozena od RoutingEffect a definuje událost pojmenovanou TouchAction a metodu, OnTouchAction která vyvolá TouchAction událost:

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);
    }
}

Všimněte si Capture také vlastnosti. Chcete-li zachytit dotykové události, musí aplikace nastavit tuto vlastnost před true událostí Pressed . Jinak se dotykové události chovají jako události v Univerzální platforma Windows.

Třída TouchActionEventArgs v knihovně .NET Standard obsahuje všechny informace, které doprovází každou událost:

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; }
}

Aplikace může vlastnost použít Id ke sledování jednotlivých prstů. IsInContact Všimněte si vlastnosti. Tato vlastnost je vždy true určena pro Pressed události a false události Released . Je to také vždy true pro události v Moved iOSu a Androidu. Vlastnost IsInContact může být false pro Moved události na Univerzální platforma Windows, když program běží na ploše a ukazatel myši se přesune bez stisknutí tlačítka.

Třídu můžete použít TouchEffect ve vlastních aplikacích zahrnutím souboru do projektu knihovny .NET Standard řešení a přidáním instance do Effects kolekce libovolného Xamarin.Forms prvku. Připojte k události obslužnou rutinu TouchAction pro získání dotykových událostí.

K použití TouchEffect ve vlastní aplikaci budete potřebovat také implementace platformy, které jsou součástí řešení TouchTrackingEffectDemos .

Implementace efektu dotykového sledování

Níže popsané implementace TouchEffect pro iOS, Android a UPW začínají nejjednodušší implementací (UPW) a končí implementací iOS, protože je strukturálně složitější než ostatní.

Implementace UPW

Implementace UPW TouchEffect je nejjednodušší. Třída je odvozena od PlatformEffect třídy a obsahuje dva atributy sestavení:

[assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.UWP.TouchEffect), "TouchEffect")]

namespace TouchTracking.UWP
{
    public class TouchEffect : PlatformEffect
    {
        ...
    }
}

Přepsání OnAttached uloží některé informace jako pole a připojí obslužné rutiny ke všem událostem ukazatele:

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;
        }
    }
    ...
}    

Obslužná rutina OnPointerPressed vyvolá událost efektu voláním onTouchAction pole v CommonHandler metodě:

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 také zkontroluje hodnotu Capture vlastnosti ve třídě efektu v knihovně .NET Standard a volá CapturePointer , pokud je true.

Další obslužné rutiny událostí UPW jsou ještě jednodušší:

public class TouchEffect : PlatformEffect
{
    ...
    void OnPointerEntered(object sender, PointerRoutedEventArgs args)
    {
        CommonHandler(sender, TouchActionType.Entered, args);
    }
    ...
}

Implementace Androidu

Implementace Androidu a iOSu jsou nutně složitější, protože musí implementovat a Entered události, Exited když se prst přesune z jednoho prvku do druhého. Obě implementace jsou strukturovány podobně.

Třída Androidu TouchEffect nainstaluje obslužnou rutinu Touch události:

view = Control == null ? Container : Control;
...
view.Touch += OnTouch;

Třída také definuje dva statické slovníky:

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>();
    ...

Získá viewDictionary novou položku při OnAttached každém zavolání přepsání:

viewDictionary.Add(view, this);

Položka je odebrána ze slovníku v .OnDetached Každá instance TouchEffect je přidružena k určitému zobrazení, ke kterému je efekt připojen. Statický slovník umožňuje, aby všechny TouchEffect instance vyčíslily všechny ostatní zobrazení a jejich odpovídající TouchEffect instance. To je nezbytné, aby bylo možné přenášet události z jednoho zobrazení do druhého.

Android přiřadí kód ID k dotykovým událostem, které aplikaci umožňují sledovat jednotlivé prsty. Tento idToEffectDictionary kód ID přidruží k TouchEffect instanci. Položka se přidá do tohoto slovníku, když Touch je obslužná rutina volána pro stisknutí prstu:

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;

Položka se odebere z idToEffectDictionary po uvolnění prstu z obrazovky. Metoda FireEvent jednoduše shromažďuje všechny informace potřebné k volání 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));
}

Všechny ostatní typy dotykového ovládání se zpracovávají dvěma různými způsoby: Pokud Capture je truevlastnost , dotyková událost je poměrně jednoduchý překlad na TouchEffect informace. Je to složitější, když Capture je to proto false , že dotykové události může být potřeba přesunout z jednoho zobrazení do jiného. Je to zodpovědnost za metodu CheckForBoundaryHop , která se volá během událostí přesunutí. Tato metoda využívá oba statické slovníky. Vytvoří výčet viewDictionary pro určení zobrazení, které se právě dotkne prstu, a používá idToEffectDictionary se k uložení aktuální TouchEffect instance (a tedy aktuálního zobrazení) přidruženého k určitému ID:

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;
    }
}

Pokud došlo ke změně idToEffectDictionary, metoda potenciálně volá FireEventExited a Entered přepojuje z jednoho zobrazení do jiného. Prst se však mohl přesunout do oblasti, která je obsazena zobrazením bez připojené TouchEffectoblasti nebo z této oblasti do zobrazení s připojeným efektem.

try Všimněte si a catch zablokujte zobrazení při přístupu k zobrazení. Na stránce, na které přejdete a pak přejde zpět na domovskou stránku, OnDetached metoda není volána a položky zůstanou v systému viewDictionary , ale Android je považuje za uvolněné.

Implementace iOS

Implementace iOS je podobná implementaci Android s tím rozdílem, že třída iOS TouchEffect musí vytvořit instanci derivátu UIGestureRecognizer. Toto je třída v projektu iOS s názvem TouchRecognizer. Tato třída udržuje dva statické slovníky, které ukládají TouchRecognizer instance:

static Dictionary<UIView, TouchRecognizer> viewDictionary =
    new Dictionary<UIView, TouchRecognizer>();

static Dictionary<long, TouchRecognizer> idToTouchDictionary =
    new Dictionary<long, TouchRecognizer>();

Většina struktury této TouchRecognizer třídy je podobná třídě Android TouchEffect .

Důležité

Mnoho zobrazení ve UIKit výchozím nastavení nemá povolené dotykové ovládání. Dotykové ovládání je možné povolit přidáním view.UserInteractionEnabled = true; přepsání OnAttached ve TouchEffect třídě v projektu iOS. K tomu by mělo dojít po UIView získání, který odpovídá prvku, ke kterému je efekt připojen.

Umístění dotykového efektu do práce

Program TouchTrackingEffectDemos obsahuje pět stránek, které testují efekt dotykového sledování pro běžné úlohy.

Stránka BoxView Draging umožňuje přidat BoxView prvky do a AbsoluteLayout potom je přetáhnout po obrazovce. Soubor XAML vytvoří instanci dvou Button zobrazení pro přidání BoxView prvků do AbsoluteLayout a vymazání AbsoluteLayoutsouboru .

Metoda v souboru kódu, který přidá nový BoxView do objektu AbsoluteLayout také TouchEffect do BoxView obslužné rutiny události k 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);
}

Obslužná rutina TouchAction události zpracovává všechny dotykové události pro všechny BoxView prvky, ale musí postupovat opatrně: Nemůže povolit dva prsty na jednom BoxView , protože program implementuje pouze přetahování a dva prsty by navzájem kolidovaly. Z tohoto důvodu stránka definuje vloženou třídu pro každý aktuálně sledovaný prst:

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>();

Obsahuje dragDictionary položku pro každou BoxView aktuálně přetahovanou položku.

Akce Pressed dotykového ovládání přidá položku do tohoto slovníku a Released akce ji odebere. Logika Pressed musí zkontrolovat, jestli už ve slovníku BoxViewexistuje položka . Pokud ano, je BoxView již přetažen a nová událost je druhý prst na stejné BoxView. U obslužné ReleasedMoved rutiny události musí zkontrolovat, jestli slovník obsahuje položku pro tuto BoxView položku a že dotyková Id vlastnost pro přetaženou BoxView položku odpovídá vlastnosti, která je v položce slovníku:

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 nastaví Capture vlastnost objektu TouchEffect na true. To má vliv na doručení všech následných událostí pro daný prst do stejné obslužné rutiny události.

Logika Moved přesune BoxView vlastnost změnou LayoutBounds připojené vlastnosti. Location Vlastnost argumentů události je vždy relativní vzhledem k BoxView přetahování a pokud BoxView je přetažena konstantní rychlostí, Location vlastnosti po sobě jdoucích událostí budou přibližně stejné. Pokud například prst stiskne BoxView v jeho středu, Pressed akce uloží PressPoint vlastnost (50, 50), která zůstane stejná pro následné události. BoxView Pokud je přetažen diagonálně konstantní rychlostí, následné Location vlastnosti během Moved akce mohou být hodnoty (55, 55), v takovém případě Moved logika přidá 5 na vodorovnou a svislou pozici BoxView. Přesune BoxView se tak, aby jeho střed byl opět přímo pod prstem.

Více prvků můžete současně přesouvat BoxView různými prsty.

Triple screenshot of the BoxView Dragging page

Podtřídy zobrazení

Často je pro prvek jednodušší Xamarin.Forms zpracovat vlastní dotykové události. Dragable BoxView Draging page funguje stejně jako BoxView Draging page, ale prvky, které uživatel přetahuje, jsou instance DraggableBoxView třídy, která je odvozena z BoxView:

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 vytvoří a připojí a nastaví TouchEffectCapture vlastnost při prvním vytvoření instance objektu. Není vyžadován žádný slovník, protože samotná třída ukládá isBeingDragged, pressPointa touchId hodnoty spojené s každým prstem. Zpracování Moved změní vlastnosti TranslationX a TranslationY vlastnosti tak, aby logika fungovala i v případě, že nadřazený objekt DraggableBoxView není .AbsoluteLayout

Integrace s SkiaSharpem

Další dvě ukázky vyžadují grafiku a pro tento účel používají SkiaSharp. Než se seznámíte s používáním SkiaSharpuXamarin.Forms, než se seznámíte s těmito příklady. První dva články ("SkiaSharp Drawing Basics" a "SkiaSharp Lines and Paths") pokrývají vše, co zde budete potřebovat.

Stránka Ellipse Drawing (Ellipse Drawing ) umožňuje kreslit tři tečky potažením prstu na obrazovce. V závislosti na tom, jak prst posunete, můžete nakreslit tři tečky z levého horního rohu do pravého dolního nebo jiného rohu do opačného rohu. Tři tečky jsou vykresleny náhodnou barvou a neprůhledností.

Triple screenshot of the Ellipse Drawing page

Pokud se pak dotknete jednoho ze tří teček, můžete ho přetáhnout na jiné místo. To vyžaduje techniku známou jako "hit-testing", která zahrnuje hledání grafického objektu v určitém okamžiku. Tři tečky SkiaSharp nejsou Xamarin.Forms prvky, takže nemohou provádět vlastní TouchEffect zpracování. Musí TouchEffect platit pro celý SKCanvasView objekt.

Soubor EllipseDrawPage.xaml vytvoří SKCanvasView instanci v jedné buňce Grid. Objekt TouchEffect je připojen k této Grid:

<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>

V Androidu a Univerzální platforma Windows TouchEffect je možné ho SKCanvasViewpřipojit přímo k systému , ale v iOSu, který nefunguje. Všimněte si, že Capture vlastnost je nastavena na truehodnotu .

Každé tři tečky, které SkiaSharp vykresluje, je reprezentován objektem 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;
    }
}

Vlastnosti StartPoint a EndPoint vlastnosti se používají při zpracování dotykového vstupu programu. Rectangle Vlastnost se používá pro kreslení tří teček. Vlastnost LastFingerLocation přichází do hry při přetahování tří teček a IsInEllipse metoda pomáhá při hit-testování. Metoda vrátí true , pokud je bod uvnitř tří teček.

Soubor kódu za kódem uchovává tři kolekce:

Dictionary<long, EllipseDrawingFigure> inProgressFigures = new Dictionary<long, EllipseDrawingFigure>();
List<EllipseDrawingFigure> completedFigures = new List<EllipseDrawingFigure>();
Dictionary<long, EllipseDrawingFigure> draggingFigures = new Dictionary<long, EllipseDrawingFigure>();

Slovník draggingFigure obsahuje podmnožinu completedFigures kolekce. Obslužná rutina události SkiaSharp PaintSurface jednoduše vykreslí objekty v těchto completedFigures kolekcích 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);
    }
}

Nejsná část zpracování dotykového ovládání je Pressed zpracování. Tady se provádí test hit-testing, ale pokud kód zjistí tři tečky pod prstem uživatele, může být toto tři tečky přetaženo pouze v případě, že není aktuálně přetažen jiným prstem. Pokud není pod prstem uživatele žádné tři tečky, začne kód nakreslit nový tři tečky:

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;

Dalším příkladem SkiaSharp je stránka Prst Malování. Můžete vybrat barvu tahu a šířku tahu ze dvou Picker zobrazení a pak kreslit jedním nebo více prsty:

Triple screenshot of the Finger Paint page

Tento příklad také vyžaduje samostatnou třídu, která představuje každou čáru malovanou na obrazovce:

class FingerPaintPolyline
{
    public FingerPaintPolyline()
    {
        Path = new SKPath();
    }

    public SKPath Path { set; get; }

    public Color StrokeColor { set; get; }

    public float StrokeWidth { set; get; }
}

Objekt SKPath se používá k vykreslení každého řádku. Soubor Prst Malování.xaml.cs udržuje dvě kolekce těchto objektů, jednu pro tyto křivky, které jsou právě nakresleny, a druhý pro dokončené čáry:

Dictionary<long, FingerPaintPolyline> inProgressPolylines = new Dictionary<long, FingerPaintPolyline>();
List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();

Zpracování Pressed vytvoří nové FingerPaintPolylinevolání MoveTo objektu cesty k uložení počátečního bodu a přidá tento objekt do slovníku inProgressPolylines . Zpracování Moved volá LineTo objekt cesty s novou polohou prstu a Released zpracování přenese dokončenou křivku z inProgressPolylines do completedPolylines. Opět je skutečný kód výkresu SkiaSharp relativně jednoduchý:

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);
    }
}

Dotykové ovládání sledování zobrazení na zobrazení

Všechny předchozí příklady nastavily Capture vlastnost TouchEffect na truehodnotu , buď při TouchEffect vytvoření, nebo při výskytu Pressed události. Tím se zajistí, že stejný prvek obdrží všechny události spojené s prstem, které poprvé stiskly zobrazení. Konečný vzorek není nastaven Capture na truehodnotu . To způsobí jiné chování, když se prst v kontaktu s obrazovkou přesune z jednoho prvku do druhého. Prvek, ze kterého se prst pohybuje, obdrží událost s vlastností nastavenou TypeTouchActionType.Exited na a druhý prvek obdrží událost s Type nastavením TouchActionType.Entered.

Tento typ dotykového zpracování je velmi užitečný pro hudební klávesnici. Klávesa by měla být schopná rozpoznat, kdy je stisknutá, ale také když se prstem posune z jedné klávesy do druhé.

Stránka Bezobslužná klávesnice definuje malé WhiteKey a BlackKey třídy, které jsou odvozeny od KeyBoxView.

Třída Key je připravena k použití ve skutečném hudebním programu. Definuje veřejné vlastnosti pojmenované IsPressed a KeyNumber, které mají být nastaveny na kód klíče vytvořený standardem MIDI. Třída Key také definuje událost s názvem StatusChanged, která je vyvolána při IsPressed změně vlastnosti.

Na každém klíči je povoleno více prstů. Z tohoto důvodu Key třída udržuje List čísla touch ID všech prstů, které se aktuálně dotknou této klávesy:

List<long> ids = new List<long>();

Obslužná rutina TouchAction události přidá ID do ids seznamu pro Pressed typ události i typ Entered , ale pouze v případě, že IsInContact je true vlastnost události Entered . ID se odebere ListReleased z události nebo Exited události:

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;
    }
}

RemoveFromList Obě AddToList metody kontrolují, jestli došlo ke List změně mezi prázdným a neprázdným, a pokud ano, vyvolá StatusChanged událost.

Různé prvky a BlackKey prvky WhiteKey jsou uspořádány do souboru XAML stránky, který vypadá nejlépe, když se telefon nachází v režimu na šířku:

Triple screenshot of the Silent Keyboard page

Pokud prst přetáhnete přes klávesy, uvidíte mírné změny barvy, že se události dotykového ovládání přenesou z jednoho klíče do druhého.

Shrnutí

Tento článek ukázal, jak vyvolat události v efektu a jak psát a používat efekt, který implementuje zpracování vícedotykového zpracování nízké úrovně.