Aracılığıyla paylaş


Efektlerden Olayları Çağırma

Bir efekt bir olayı tanımlayıp çağırarak temel alınan yerel görünümdeki değişikliklere işaret edebilir. Bu makalede, düşük düzeyli çok dokunmalı parmak izlemenin nasıl uygulanacağı ve dokunma etkinliği sinyali veren olayların nasıl oluşturulacağı gösterilmektedir.

Bu makalede açıklanan etki, düşük düzeyli dokunma olaylarına erişim sağlar. Bu alt düzey olaylar mevcut GestureRecognizer sınıflar aracılığıyla kullanılamaz, ancak bazı uygulama türleri için çok önemlidir. Örneğin, bir parmak boyası uygulamasının ekranda hareket eden tek tek parmaklarını izlemesi gerekir. Müzik klavyesinin tek tek tuşlardaki dokunmaları ve yayınları algılaması ve glissandoda bir tuştan diğerine kayan bir parmağı algılaması gerekir.

Bir efekt, herhangi bir Xamarin.Forms elemana bağlanabildiği için çok dokunmalı parmak izleme için idealdir.

Platform Dokunma Olayları

iOS, Android ve Evrensel Windows Platformu, uygulamaların dokunma etkinliğini algılamasına olanak tanıyan düşük düzeyli bir API içerir. Bu platformların tümü üç temel dokunma olayı türünü birbirinden ayırır:

  • Basıldığında, bir parmak ekrana dokunduğunda
  • Taşındı, ekrana dokunan bir parmak hareket ettiğinde
  • Serbest bırakıldığında, parmak ekrandan serbest bırakıldığında

Çok dokunmalı bir ortamda, birden çok parmak ekrana aynı anda dokunabilir. Çeşitli platformlar, uygulamaların birden çok parmak arasında ayrım yapmak için kullanabileceği bir kimlik (KIMLIK) numarası içerir.

iOS'ta UIView sınıfı, bu üç temel olaya karşılık gelen ve TouchesEnded geçersiz kılınabilir üç yöntem TouchesBeganTouchesMovedtanımlar. Multi-Touch Finger Tracking makalesinde bu yöntemlerin nasıl kullanılacağı açıklanmaktadır. Ancak, bir iOS programının bu yöntemleri kullanmak için türetilen UIView bir sınıfı geçersiz kılması gerekmez. iOS UIGestureRecognizer aynı üç yöntemi de tanımlar ve öğesinden UIGestureRecognizer türetilen bir sınıfın örneğini herhangi UIView bir nesneye ekleyebilirsiniz.

Android'de sınıfı, View tüm dokunma etkinliğini işlemek için adlı OnTouchEvent geçersiz kılınabilir bir yöntem tanımlar. Dokunma etkinliğinin türü, Multi-Touch Parmak İzleme makalesinde açıklandığı gibi numaralandırma üyeleri Down, PointerDownMove, , Upve PointerUp tarafından tanımlanır. AndroidView, bir olay işleyicisinin herhangi View bir nesneye eklenmesini sağlayan adlı Touch bir olayı da tanımlar.

Evrensel Windows Platformu (UWP) sınıfında UIElement sınıfı , PointerMovedve PointerReleasedadlı PointerPressedolayları tanımlar. Bunlar MSDN'deki İşaretçi Girişini İşleme makalesinde ve sınıfın API belgelerinde UIElement açıklanmıştır.

Pointer Evrensel Windows Platformu API'sinin amacı fare, dokunma ve kalem girişini birleştirmektir. Bu nedenle, PointerMoved fare düğmesine basılmadığında bile fare bir öğe üzerinde hareket ettiğinde olay çağrılır. Bu PointerRoutedEventArgs olaylara Pointer eşlik eden nesne, bir fare düğmesine basıldığını veya parmağın ekranla temas halinde olduğunu belirten adlı IsInContact bir özelliğe sahiptir.

Ayrıca UWP, ve PointerExitedadlı PointerEntered iki olay daha tanımlar. Bunlar, bir farenin veya parmağın bir öğeden diğerine ne zaman hareket ettiğini gösterir. Örneğin, A ve B adlı iki bitişik öğeyi göz önünde bulundurun. her iki öğe de işaretçi olayları için işleyiciler yükledi. Bir parmak A'ya bastığında PointerPressed olay çağrılır. Parmak hareket ettikçe, A olayları çağırır PointerMoved . Parmak A'dan B'ye geçerse, A bir PointerExited olayı çağırır ve B bir PointerEntered olayı çağırır. Parmak serbest bırakılırsa, B bir PointerReleased olayı çağırır.

iOS ve Android platformları UWP'den farklıdır: İlk çağrıyı TouchesBegan alan veya OnTouchEvent bir parmak dokunduğunda görünüme dokunan görünüm, parmak farklı görünümlere geçse bile tüm dokunma etkinliğini almaya devam eder. Uygulama işaretçiyi yakalarsa UWP benzer şekilde davranabilir: Olay işleyicisinde PointerEntered öğe çağrılar CapturePointer ve ardından tüm dokunma etkinliğini bu parmaktan alır.

UWP yaklaşımı, müzik klavyesi gibi bazı uygulama türleri için çok yararlı olduğunu kanıtlar. Her tuş, bu tuş için dokunma olaylarını işleyebilir ve ve PointerExited olaylarını kullanarak PointerEntered bir parmağın bir anahtardan diğerine ne zaman kaydırıldığını algılayabilir.

Bu nedenle, bu makalede açıklanan dokunma izleme etkisi UWP yaklaşımını uygular.

Dokunma İzleme Efekti API'si

Örnek, alt düzey dokunma izlemeyi uygulayan sınıfları (ve bir numaralandırmayı) içerir. Bu türler ad alanına TouchTracking aittir ve sözcüğüyle Touchbaşlar. TouchTrackingEffectDemos .NET Standard kitaplık projesi, dokunma olaylarının türüne yönelik numaralandırmayı içerirTouchActionType:

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

Tüm platformlar, dokunma olayının iptal edildiğini gösteren bir olay da içerir.

TouchEffect.NET Standard kitaplığındaki sınıfı, adlı bir olaydan RoutingEffectTouchAction türetilir ve olayı çağıran TouchAction adlı OnTouchAction bir yöntemi tanımlar:

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

Ayrıca özelliğine Capture de dikkat edin. Dokunma olaylarını yakalamak için uygulamanın bu özelliği bir olaydan önce olarak true ayarlaması Pressed gerekir. Aksi takdirde, dokunma olayları Evrensel Windows Platformu gibi davranır.

TouchActionEventArgs.NET Standard kitaplığındaki sınıfı, her olaya eşlik eden tüm bilgileri içerir:

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

Bir uygulama, tek tek parmaklarını Id izlemek için özelliğini kullanabilir. özelliğine IsInContact dikkat edin. Bu özellik her zaman true olaylar ve Pressedfalse olaylar içindir Released . Ayrıca her zaman true iOS ve Android'de etkinlikler içindir Moved . Özellik, IsInContact program masaüstünde çalışırken ve fare işaretçisi düğmeye basılmadan hareket ettiğinde Evrensel Windows Platformu olaylar için Moved olabilirfalse.

kendi uygulamalarınızda sınıfınıTouchEffect, dosyayı çözümün .NET Standard kitaplık projesine ekleyerek ve herhangi Xamarin.Forms bir öğenin koleksiyonuna Effects bir örnek ekleyerek kullanabilirsiniz. Dokunma olaylarını TouchAction almak için olaya bir işleyici ekleyin.

Kendi uygulamanızda kullanmak TouchEffect için, TouchTrackingEffectDemos çözümüne dahil edilen platform uygulamalarına da ihtiyacınız olacaktır.

Dokunma İzleme Efekti Uygulamaları

uygulamasının TouchEffect iOS, Android ve UWP uygulamaları aşağıda en basit uygulamadan (UWP) başlayıp iOS uygulamasıyla sona erer çünkü diğerlerinden daha yapısal olarak karmaşıktır.

UWP Uygulaması

UWP uygulaması TouchEffect en basittir. Her zamanki gibi sınıfından türetilir PlatformEffect ve iki derleme özniteliği içerir:

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

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

Geçersiz OnAttached kılma bazı bilgileri alan olarak kaydeder ve tüm işaretçi olaylarına işleyiciler ekler:

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

İşleyici, OnPointerPressed yöntemindeki alanını CommonHandler çağırarak onTouchAction efekt olayını çağırır:

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

OnPointerPressedayrıca .NET Standard kitaplığındaki efekt sınıfındaki özelliğinin değerini Capture denetler ve ise trueçağırırCapturePointer.

Diğer UWP olay işleyicileri daha da basittir:

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

Android Uygulaması

Bir parmak bir öğeden diğerine geçtiğinde ve olaylarını uygulaması gerektiğinden Exited Android ve Entered iOS uygulamaları mutlaka daha karmaşıktır. Her iki uygulama da benzer şekilde yapılandırılmıştır.

Android TouchEffect sınıfı, olay için Touch bir işleyici yükler:

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

sınıfı ayrıca iki statik sözlük tanımlar:

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

geçersiz viewDictionary kılma her çağrıldığında OnAttached yeni bir girdi alır:

viewDictionary.Add(view, this);

girdisi içindeki OnDetachedsözlükten kaldırılır. her örneği TouchEffect , efektin eklendiği belirli bir görünümle ilişkilendirilir. Statik sözlük, tüm TouchEffect örneklerin diğer tüm görünümleri ve ilgili TouchEffect örneklerini listelemesine olanak tanır. Bu, olayların bir görünümden diğerine aktarılmasına izin vermek için gereklidir.

Android, bir uygulamanın tek tek parmaklarını izlemesine olanak tanıyan dokunma olaylarına bir kimlik kodu atar. bu idToEffectDictionary kimlik kodunu bir TouchEffect örnekle ilişkilendirir. İşleyici parmak basmak için çağrıldığında Touch bu sözlüğe bir öğe eklenir:

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;

Öğe, parmak ekrandan serbest bırakıldığında öğesinden kaldırılır idToEffectDictionary . FireEvent yöntemi yalnızca yöntemini çağırmak için gereken tüm bilgileri biriktirirOnTouchAction:

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

Diğer tüm dokunma türleri iki farklı şekilde işlenir: özelliği ise Capturetrue, dokunma olayı bilgilere oldukça basit bir çeviridir TouchEffect . Dokunma olaylarının false bir görünümden diğerine taşınması gerekebileceği durumlarda Capture daha karmaşık hale gelir. Bu, taşıma olayları sırasında çağrılan yönteminin sorumluluğudur CheckForBoundaryHop . Bu yöntem her iki statik sözlükleri de kullanır. Parmağın viewDictionary şu anda dokunduğu görünümü belirlemek için öğesini numaralandırır ve belirli bir kimlikle ilişkili geçerli TouchEffect örneği (ve dolayısıyla geçerli görünümü) depolamak için kullanıridToEffectDictionary:

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

içinde idToEffectDictionarybir değişiklik yapıldıysa yöntemi, FireEvent bir görünümden diğerine aktarmak için Exited ve Entered çağrıları yapabilir. Bununla birlikte, parmak eklenmiş TouchEffectolmayan bir görünümün kapladığı bir alana veya bu alandan efekt eklenmiş bir görünüme taşınmış olabilir.

Görünüme try erişildiğinde ve catch bloğuna dikkat edin. Bu sayfaya gidilen ve giriş sayfasına geri gidilen bir sayfada OnDetached yöntem çağrılmaz ve öğeler içinde viewDictionary kalır, ancak Android bunları atılmış olarak kabul eder.

iOS Uygulaması

iOS uygulaması Android uygulamasına benzer, ancak iOS TouchEffect sınıfının bir türevini oluşturması UIGestureRecognizergerekir. Bu, iOS projesinde adlı TouchRecognizerbir sınıftır. Bu sınıf, örnekleri depolayan TouchRecognizer iki statik sözlük tutar:

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

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

Bu TouchRecognizer sınıfın yapısının çoğu Android TouchEffect sınıfına benzer.

Önemli

uygulamasındaki görünümlerin UIKit çoğunda dokunmatik ekran varsayılan olarak etkin değildir. Dokunma, iOS projesindeki OnAttachedTouchEffect sınıftaki geçersiz kılmaya eklenerek view.UserInteractionEnabled = true; etkinleştirilebilir. Bu, efektin UIView eklendiği öğeye karşılık gelen elde edildikten sonra gerçekleşmelidir.

Dokunma Efektini Çalışır Duruma Getirmek

Örnek program, ortak görevler için dokunma izleme etkisini test eden beş sayfa içerir.

BoxView Sürükleme sayfası, öğesine öğe eklemenize BoxViewAbsoluteLayout ve ardından bunları ekranda sürüklemenize olanak tanır. XAML dosyası, öğesine öğe AbsoluteLayout eklemek BoxView ve öğesini temizlemek için iki Button görünümün örneğini AbsoluteLayoutoluşturur.

'a AbsoluteLayout yeni bir ekleyen arka planda kod dosyasındakiyöntemi, öğesine bir TouchEffect nesnesi BoxView ekler ve efekte bir olay işleyicisi BoxView ekler:

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

Olay işleyicisi TouchAction tüm BoxView öğeler için tüm dokunma olaylarını işler, ancak biraz dikkatli olması gerekir: Program yalnızca sürükleme uyguladığından ve iki parmağın birbirine karışması nedeniyle tek bir BoxView parmak üzerinde iki parmağın kullanılmasına izin vermez. Bu nedenle, sayfa şu anda izlenmekte olan her parmak için eklenmiş bir sınıf tanımlar:

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

şu dragDictionary anda sürüklenen her BoxView için bir girdi içerir.

Dokunma Pressed eylemi bu sözlüğe bir öğe ekler ve eylem öğeyi Released kaldırır. Mantığın Pressed sözlüğe ait bir öğe olup olmadığını denetlemesi BoxViewgerekir. Öyleyse, zaten sürükleniyor BoxView ve yeni olay aynı BoxViewüzerinde ikinci parmaktır. Moved ve Released eylemleri için, olay işleyicisi sözlüğün bunun BoxView için bir girdisi olup olmadığını ve sürüklenen BoxView sözlüğe ait touch Id özelliğinin sözlük girdisindekiyle eşleşip eşleşmediğini denetlemelidir:

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

Mantık, Pressed nesnesinin CaptureTouchEffect özelliğini olarak trueayarlar. Bu, bu parmak için sonraki tüm olayları aynı olay işleyicisine teslim etme etkisine sahiptir.

Mantık, Moved ekli özelliği değiştirerek LayoutBounds öğesini taşırBoxView. Location Olay bağımsız değişkenlerinin özelliği her zaman sürüklenene BoxView göredir ve BoxView sabit bir hızda sürükleniyorsa ardışık Location olayların özellikleri yaklaşık olarak aynı olur. Örneğin, bir parmak ortasındakine BoxView basarsa, Pressed eylem sonraki olaylar için aynı kalan ( PressPoint 50, 50) özelliğini depolar. BoxView sabit bir hızda çapraz olarak sürüklenirse, eylem sırasındaki Moved sonraki Location özellikler değeri (55, 55) olabilir ve bu durumda Moved mantık öğesinin yatay ve dikey konumuna BoxView5 ekler. Bu, merkezi yeniden doğrudan parmağın altında olacak şekilde hareket eder BoxView .

Farklı parmaklar kullanarak birden çok BoxView öğeyi aynı anda taşıyabilirsiniz.

BoxView Sürükleme sayfasının üçlü ekran görüntüsü

Görünümü Alt Sınıflama

Çoğu zaman, bir Xamarin.Forms öğenin kendi dokunma olaylarını işlemesi daha kolaydır. Sürüklenebilir BoxView Sürükleme sayfası, BoxView Sürükleme sayfasıyla aynı işlevi görür, ancak kullanıcının sürüklediği öğeler öğesinden BoxViewtüretilen bir DraggableBoxView sınıfın örnekleridir:

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

Oluşturucu, öğesini oluşturup ekler TouchEffectve bu nesne ilk kez başlatıldığında özelliğini ayarlar Capture . Sınıfın kendisi her parmakla ilişkili , pressPointve touchId değerlerini depoladığı isBeingDraggediçin sözlük gerekmez. İşlemeMoved, ve TranslationY özelliklerini değiştirirTranslationX, böylece mantığın üst öğesi DraggableBoxView bir AbsoluteLayoutolmasa bile çalışır.

SkiaSharp ile tümleştirme

Sonraki iki gösterim için grafik gerekir ve bu amaçla SkiaSharp kullanılır. Bu örnekleri incelemeden önce SkiaSharp kullanma Xamarin.Forms hakkında bilgi edinmek isteyebilirsiniz. İlk iki makale ("SkiaSharp Çizim Temel Bilgileri" ve "SkiaSharp Çizgileri ve Yolları"), burada ihtiyacınız olacak her şeyi kapsar.

Üç Nokta Çizim sayfası, parmağınızı ekranda çekerek bir elips çizmenizi sağlar. Parmağınızı nasıl hareket ettirdiğinize bağlı olarak, elipsleri sol üst köşeden sağ alta veya başka bir köşeden karşı köşeye çizebilirsiniz. Üç nokta rastgele bir renk ve opaklık ile çizilir.

Üç Nokta Çizim sayfasının üçlü ekran görüntüsü

Daha sonra üç noktalardan birine dokunursanız, başka bir konuma sürükleyebilirsiniz. Bu, belirli bir noktada grafik nesnenin aranarak "isabet testi" olarak bilinen bir teknik gerektirir. SkiaSharp üç nokta öğeleri değildir Xamarin.Forms , bu nedenle kendi TouchEffect işlemlerini gerçekleştiremezler. tüm TouchEffectSKCanvasView nesneye uygulanmalıdır.

EllipseDrawPage.xaml dosyası, tek hücreli Gridiçinde örneğini SKCanvasView oluşturur. TouchEffect nesnesi bu Gridnesneye eklenir:

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

Android'de ve Evrensel Windows Platformu doğrudan TouchEffect öğesine eklenebilirSKCanvasView, ancak iOS'ta çalışmaz. özelliğinin Capture olarak ayarlandığına truedikkat edin.

SkiaSharp tarafından işlenen her üç nokta türündeki EllipseDrawingFigurebir nesneyle temsil edilir:

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

StartPoint program dokunmatik girişi işlerken ve EndPoint özellikleri kullanılır; Rectangle özelliği üç nokta çizmek için kullanılır. LastFingerLocation Üç nokta sürüklenirken özelliği devreye girer ve IsInEllipse yöntem isabet testine yardımcı olur. Yöntem, nokta üç noktanın içindeyse döndürür true .

Arka planda kod dosyası üç koleksiyon tutar:

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

Sözlük, draggingFigure koleksiyonun completedFigures bir alt kümesini içerir. SkiaSharp PaintSurface olay işleyicisi yalnızca ve completedFiguresinProgressFigures koleksiyonlarındaki nesneleri işler:

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

Dokunma işleminin en karmaşık kısmı işlemedir Pressed . Burada isabet testi gerçekleştirilir, ancak kod kullanıcının parmağı altında bir üç nokta algılarsa, bu üç nokta yalnızca şu anda başka bir parmak tarafından sürüklenmediyse sürüklenebilir. Kullanıcının parmağında üç nokta yoksa kod yeni bir üç nokta çizme işlemini başlatır:

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;

Diğer SkiaSharp örneği, Finger Paint sayfasıdır. İki Picker görünümden bir vuruş rengi ve vuruş genişliği seçebilir ve ardından bir veya daha fazla parmakla çizim yapabilirsiniz:

Finger Paint sayfasının üçlü ekran görüntüsü

Bu örnek, ekranda boyanan her çizgiyi temsil etmek için ayrı bir sınıf gerektirir:

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

    public SKPath Path { set; get; }

    public Color StrokeColor { set; get; }

    public float StrokeWidth { set; get; }
}

SKPath Her satırı işlemek için bir nesne kullanılır. FingerPaint.xaml.cs dosyası, biri şu anda çizilmekte olan çok çizgili ve diğeri tamamlanmış çok çizgili nesneler için olmak üzere bu nesnelerin iki koleksiyonunu tutar:

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

İşleme, Pressed ilk noktayı depolamak için yol nesnesi üzerinde yeni FingerPaintPolylinebir oluşturur MoveTo ve bu nesneyi inProgressPolylines sözlüğe ekler. İşleme Moved , yeni parmak konumuyla yol nesnesine çağrılar LineTo ve Released işlem tamamlanan çok çizgiliyi 'den'e inProgressPolylinescompletedPolylinesaktarır. Bir kez daha, gerçek SkiaSharp çizim kodu nispeten basittir:

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

Görünümden Görünüme Dokunmayı İzleme

Önceki örneklerin tümü, öğesinin CaptureTouchEffectTouchEffecttrueözelliğini oluşturulduğunda veya olay oluştuğunda Pressed olarak ayarlamıştır. Bu, aynı öğenin görünüme ilk basan parmakla ilişkili tüm olayları almasını sağlar. Son örnek olarak trueayarlanmadıCapture. Bu, ekranla temas eden bir parmak bir öğeden diğerine geçtiğinde farklı davranışlara neden olur. Parmağın hareket eden öğesi, özelliği ayarlanmış TouchActionType.Exited bir Type olay alır ve ikinci öğe ayarına TouchActionType.Enteredsahip bir Type olay alır.

Bu tür dokunmatik işleme, bir müzik klavyesi için çok kullanışlıdır. Bir tuş basıldığında algılayabilmelidir, ancak bir parmak bir tuştan diğerine kaydığında da algılayabilmelidir.

Sessiz Klavye sayfası, öğesinden Keytüretilen küçük WhiteKey ve BlackKey sınıflarını BoxViewtanımlar.

sınıfı Key , gerçek bir müzik programında kullanılmaya hazırdır. ve KeyNumberadlı IsPressed ortak özellikleri tanımlar. Bu, MIDI standardı tarafından oluşturulan anahtar koduna ayarlanması amaçlanmıştır. sınıfı, Key özelliği değiştiğinde IsPressed çağrılan adlı StatusChangedbir olayı da tanımlar.

Her tuşa birden çok parmakla izin verilir. Bu nedenle, Key sınıfı şu anda bu tuşa dokunan tüm parmaklarının dokunmatik kimlik numaralarından birini List tutar:

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

Olay işleyicisiTouchAction, hem olay türü hem de Pressed türü Entered için listeye bir kimlik ids ekler, ancak yalnızca özelliği true olay için Entered olduğundaIsInContact. Kimlik, bir Released veya Exited olayı için öğesinden List kaldırılır:

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

ve yöntemlerinin AddToList her ikisi de öğesinin List boş ile boş olmayan arasında değişip değişmediğini denetler ve değiştirildiğinde olayı çağırırStatusChanged.RemoveFromList

Çeşitli WhiteKey ve BlackKey öğeler sayfanın XAML dosyasında düzenlenir ve telefon yatay modda tutulduğunda en iyi şekilde görünür:

Sessiz Klavye sayfasının üçlü ekran görüntüsü

Parmağınızı tuşlar boyunca süpürürseniz, dokunma olaylarının bir tuştan diğerine aktarıldığını renkli küçük değişikliklerle görürsünüz.

Özet

Bu makalede, bir efektteki olayların nasıl çağrıldığı ve düşük düzeyli çok dokunmalı işleme uygulayan bir efektin nasıl yazıldığı ve kullanılacağı gösterilmiştir.