Drawing (1)

To study how to draw some sort of figures such as lines and circles etc., and how to tap&drag those items, I developed a sample program referring to the official site of SkiaSharp and Inventing Events from Effects.

In this sample program, correlation diagrams of novel characters can be easily created and editted.  Inputting characters group names in the 1st (main) page, they are listed in the ListView of the page.  If tap one of the name (the list item), move to the page where the correlation diagram of the tapped characters group can be created and editted.



MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:views="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="DrawTouch.MainPage">

    <StackLayout x:Name="stackLayout" Margin="0, 20, 0, 0">
        <Label Text="Drawing and Touch/Tracking Demo:" />
        <Label Text="    Book Characters Correlation Diagram" />
        <Label Text="----------------------------------------" />

        <!-- Add a new Character Group page to PageListView-->
        <StackLayout Orientation="Horizontal">
            <StackLayout>
                <Entry x:Name="CharaGroupTitle" Text="" Placeholder="Characters Group" Keyboard="Text" WidthRequest="250"/>
                <Entry x:Name="Characters" Text="" Placeholder="Characters" Keyboard="Text" WidthRequest="250">
                    <!-- Enable this Entry, if CharaGroupTitle-Entry gets text input -->
                    <Entry.Triggers>
                        <DataTrigger TargetType="Entry" Binding="{Binding Source={x:Reference CharaGroupTitle}, Path=Text.Length}" Value="0">
                            <Setter Property="IsEnabled" Value="False" />
                        </DataTrigger>
                    </Entry.Triggers>
                </Entry>
            </StackLayout>
            <Button Text="Add Group" Clicked="AddPage" BorderColor="Blue" BorderWidth="1">
                <!-- Enable this Button, if CharaGroupTitle-Entry gets text input -->
                <Button.Triggers>
                    <DataTrigger TargetType="Button" Binding="{Binding Source={x:Reference CharaGroupTitle}, Path=Text.Length}" Value="0">
                        <Setter Property="IsEnabled" Value="False" />
                    </DataTrigger>
                </Button.Triggers>
            </Button>
        </StackLayout>

        <!-- Create a Character Group page List "PageListView"
                 Call "JumpPage" method, if one of the items (pages) is tapped -->
        <ListView x:Name="PageListView" ItemTapped="JumpPage">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextCell
                            Text="{Binding Title, Mode=TwoWay}" 
                            Detail="{Binding Characters, Mode=TwoWay}" 
                            TextColor="Black" DetailColor="DimGray" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

    </StackLayout>

    <!-- Change color, if Entry is focused -->
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Entry">
                <Style.Triggers>
                    <Trigger  TargetType="Entry" Property="IsFocused" Value="True">
                        <Setter Property="BackgroundColor" Value="Aqua" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>

</ContentPage>
(This source code is also here.)

Create the contents of the main page.  The Entry of CharaGroupTitle and Characters get text input, then AddPage method in MainPage.xaml.cs is called, and the characters group name is listed in PageListView.  If one of the characters group names is tapped, JumpPage method in MainPage.xaml.cs navigates to the page for characters correlation diagram creation.



MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;

using Xamarin.Forms;

using SkiaSharp;
using SkiaSharp.Views.Forms;

using TouchTracking;

namespace DrawTouch
{
    public partial class MainPage : ContentPage
    {
        public class Page
        {
            public string Title { getset; }
            public string Characters { getset; }
        }

        public static ObservableCollection<PagePages;

        public class Link
        {
            public bool exist;            // true, if link exists
            public int x1y1x2y2;    // start and end points
        }

        Dictionary<ContentView, DragInfo> dragDictionary = new Dictionary<ContentView, DragInfo>();

        // set some array variables to memory the corresponding variable
        static int maxpage = 20;
        static int maxnode = 10;
        ContentPage[] contentPages = new ContentPage[maxpage];
        SKCanvasView[] canvasViews = new SKCanvasView[maxpage];
        AbsoluteLayout[] absoluteLayouts = new AbsoluteLayout[maxpage];
        ContentView[,] node = new ContentView[maxpagemaxnode];
        Link[,,] link = new Link[maxpagemaxnodemaxnode];
        string[,,] linkTitle = new string[maxpagemaxnodemaxnode];
        
        int page = 0;   // Current Page
        int NoP = 0;    // The Number of Page
        int[] NoN = Enumerable.Repeat<int>(0maxnode).ToArray(); //The Number of Nodes in each Page

        int linkStartlinkEnd = 0;    // temporary variable
        string entryText = "";        // temporary variable
        int nodeID = 0;             // temporary variable

        int scale = 2;  // canvas scale:  2(iOS),  1(UWP)
        int space = 7;  // space between two arrows with opposite direction
        

        public MainPage()
        {
            InitializeComponent();

            // Initialize PageListView
            Pages = new ObservableCollection<Page>()
            {
                new Page(){Title="No Groups"Characters="" }
            };
            PageListView.ItemsSource = Pages;

            // Initialize link[,,]
            for (int i = 0i < 20i++)
            {
                for (int j = 0j < 10j++)
                {
                    for (int k = 0k < 10k++)
                    {
                        link[ijk] = new Link { exist = falsex1 = 0y1 = 0x2 = 0y2 = 0 };
                    }
                }
            }
        }


        // Add Character Group Title to PageListView, and 
        // Create a new Character Group Page to draw the characters correlation diagram
        void AddPage(object senderEventArgs args)
        {
            // Add Character Group Title to PageListView
            Page new_page = new Page()
            {
                Title = CharaGroupTitle.Text,
                Characters = Characters.Text
            };
            if (NoP == 0Pages.Clear(); // Delete "No Pages" message, if 1st item was added 
            Pages.Add(new_page);


            // stacklayoutTop1
            Entry AddNodeEntry = new Entry
            {
                Placeholder = "Character Name"
                WidthRequest=230
            };
            AddNodeEntry.TextChanged += AddNodeEntryTextChanged;
            Button AddNodeButton = new Button
            {
                Text = "Add Character",
                BorderColor = Color.Blue,
                BorderWidth = 1
                HorizontalOptions = LayoutOptions.EndAndExpand, 
                WidthRequest=120
            };
            AddNodeButton.Clicked += AddNode;
            StackLayout stacklayoutTop1 = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                Children =
                {
                    AddNodeEntry, 
                    AddNodeButton
                }
            };

            // stacklayoutTop2
            Entry AddLinkEntry = new Entry
            {
                Placeholder = "Link Name",
                WidthRequest = 230
            };
            AddLinkEntry.TextChanged += AddLinkEntryTextChanged;
            Button AddLinkButton = new Button
            {
                Text = "Add Link"
                BorderColor = Color.Blue,
                BorderWidth = 1
                HorizontalOptions = LayoutOptions.EndAndExpand, 
                WidthRequest=120
            };
            AddLinkButton.Clicked += AddLink;
            StackLayout stacklayoutTop2 = new StackLayout
            {
                Orientation=StackOrientation.Horizontal, 
                Children =
                {
                    AddLinkEntry,
                    AddLinkButton
                }
            };

            // Character Group Title
            Label charagroupTitle = new Label
            {
                Text = CharaGroupTitle.Text,
                FontSize = 25
            };


            // create SkiaSharp canvas and AbsoluteLayout to draw CharacterNode
            SKCanvasView canvas = new SKCanvasView();
            canvas.PaintSurface += OnCanvasViewPaintSurface;

            AbsoluteLayout absoluteLayout = new AbsoluteLayout    { };

            Grid gridCanvas = new Grid
            {
                HeightRequest = 500
                WidthRequest = 300,
                BackgroundColor = Color.Gray,
                Children = {
                    canvas, 
                    absoluteLayout
                }
            };

            //stacklayoutBottom
            Button PreviousPage = new Button
            {
                Text="<="
                FontSize=20
                HorizontalOptions=LayoutOptions.Start
            };
            PreviousPage.Clicked += MovePreviousPage;
            Label LabelPageNo = new Label
            {
                Text = "Page " + (NoP + 1).ToString(),
                FontSize = 20
                HorizontalOptions=LayoutOptions.CenterAndExpand
            };
            Button TopPage = new Button
            {
                Text = "Top Page",
            };
            TopPage.Clicked += PopToRoot;
            Button NextPage = new Button
            {
                Text = "=>"
                FontSize=20,
                HorizontalOptions = LayoutOptions.EndAndExpand
            };
            NextPage.Clicked += MoveNextPage;
            StackLayout stacklayoutBottom = new StackLayout
            {
                Orientation = StackOrientation.Horizontal,
                Children = {
                    PreviousPage,
                    TopPage,
                    LabelPageNo, 
                    NextPage
                }
            };

            ContentPage contentPage = new ContentPage
            {
                Content = new StackLayout
                {
                    Children =
                    {
                        stacklayoutTop1, 
                        stacklayoutTop2,
                        charagroupTitle,
                        gridCanvas,                        
                        stacklayoutBottom
                    }
                }
            };

            // store the value of the current contentPage, absoluteLayout and canvas
            contentPages[NoP] = contentPage;
            absoluteLayouts[NoP] = absoluteLayout;
            canvasViews[NoP] = canvas;
            NoP++;
            CharaGroupTitle.Text = ""; Characters.Text = "";  // Clear text input in Entry
        }


        // Get the page number of the tapped item and jump to the page
        async void JumpPage(object sender, ItemTappedEventArgs e)
        {
            var index = Pages.IndexOf((Page)e.Item);
            await Navigation.PushAsync(contentPages[index]);

            page = (int)index;  // set Current Page Number
        }


        // Add Character Node
        Entry entry = new Entry();
        void AddNodeEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            entry = sender as Entry;
            entryText = entry.Text;
        }
        void AddNode(object senderEventArgs args)
        {
            AddNodeBox(absoluteLayouts[page]);

            entry.Text = "";
            NoN[page]++;
        }

        // set flag to control node selection for linking
        void AddLinkEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            entry = sender as Entry;
            entryText = entry.Text;
        }
        int AddLinkFlag = 0;    // Initialize AddArrowFlag
        void AddLink(object senderEventArgs args)
        {
            AddLinkFlag = 1;    // 1st node has been selected

            // Initialize Node Boarder Color (Red -> Black) 
            for (int i = 0i < NoN[page]; i++)
            {
                node[pagei].BackgroundColor = Color.Black;
            }
        }


        // Page Control
        async void MovePreviousPage(object senderEventArgs args)
        {
            if (page == 0)
            {
                await Navigation.PopToRootAsync();
                return;
            }
            page--;
            await Navigation.PopAsync();
        }
        async void MoveNextPage(object senderEventArgs args)
        {
            if (page == (NoP-1))
            {
                await DisplayAlert("",page.ToString(), "OK");
                return;
            }
            page++;
            await Navigation.PushAsync(contentPages[page]);
        }
        async void PopToRoot(object senderEventArgs args)
        {
            await Navigation.PopToRootAsync();
        }


        // add Character Node
        void AddNodeBox(AbsoluteLayout absLayout)
        {
            Entry CharacterName = new Entry
            {
                FontSize = 20,
                BackgroundColor = Color.White,
                InputTransparent = true
                Text=entryText
            };

            ContentView contentView = new ContentView
            {
                BackgroundColor = Color.Black, 
                Padding = new Thickness(3),
                Content=new StackLayout {
                    Children = { CharacterName }
                }
            };


            node[pageNoN[page]] = contentView;

            // detect tap event
            var tap = new TapGestureRecognizer();
            tap.Tapped += (se) =>
            {
                if(AddLinkFlag==1)        // 1st node has already been selected
                {
                    AddLinkFlag = 2;    // 2nd node has been selected
                    ContentView view = contentView as ContentView;
                    Rectangle rect = AbsoluteLayout.GetLayoutBounds(view);

                    for (int i = 0i < NoN[page]; i++){
                        if (view == node[pagei]){
                            linkStart = ibreak;
                        } 
                    }

                    view.BackgroundColor = Color.Red;
                    return;
                }
                else if(AddLinkFlag==2)    // 2nd node has already been selected
                {
                    AddLinkFlag = 0;    //reset AddLinkFlag
                    ContentView view = contentView as ContentView;
                    Rectangle rect = AbsoluteLayout.GetLayoutBounds(view);

                    for (int i = 0i < NoN[page]; i++)
                    {
                        if (view == node[pagei]) {
                            linkEnd = ibreak;
                        }
                    }

                    // set initial link position to the center of the each node
                    link[pagelinkStartlinkEnd].exist = true;
                    link[pagelinkStartlinkEnd].x1 = (int)((node[page,linkStart].X + node[page,linkStart].Width/2) * scale);
                    link[pagelinkStartlinkEnd].y1 = (int)((node[page,linkStart].Y + node[page,linkStart].Height/2) * scale);
                    link[pagelinkStartlinkEnd].x2 = (int)((node[page,linkEnd].X + node[page,linkEnd].Width/2) * scale);
                    link[pagelinkStartlinkEnd].y2 = (int)((node[page,linkEnd].Y + node[page,linkEnd].Height/2) * scale);
                    linkTitle[pagelinkStartlinkEnd] = entryText;

                    view.BackgroundColor = Color.Red;
                    entry.Text = ""// clear AddLinkEntry
                    return;
                }
            };
            contentView.GestureRecognizers.Add(tap);

            TouchEffect touchEffect = new TouchEffect();
            touchEffect.TouchAction += OnTouchEffectAction;
            contentView.Effects.Add(touchEffect);
            absLayout.Children.Add(contentView);
        }


        void OnTouchEffectAction(object sender, TouchActionEventArgs args)
        {
            ContentView view = sender as ContentView;
            SKCanvasView canvas = new SKCanvasView();

            canvas = canvasViews[page];

            // identify the touched node
            for (int i = 0i < NoN[page]; i++)
            {
                if (view == node[pagei]) { nodeID = i;   break; }
            }

            switch (args.Type)
            {
                case TouchActionType.Pressed:
                    // Don't allow a second touch on an already touched BoxView
                    if (!dragDictionary.ContainsKey(view))
                    {
                        dragDictionary.Add(viewnew DragInfo(args.Id, args.Location));

                        // Set Capture property to true
                        TouchEffect touchEffect = (TouchEffect)view.Effects.FirstOrDefault(e => e is TouchEffect);
                        touchEffect.Capture = true;
                    }
                    break;

                case TouchActionType.Moved:
                    if (dragDictionary.ContainsKey(view) && dragDictionary[view].Id == args.Id)
                    {
                        // update all touched node posisions
                        Rectangle rect = AbsoluteLayout.GetLayoutBounds(view);
                        Point initialLocation = dragDictionary[view].PressPoint;
                        rect.X += args.Location.X - initialLocation.X;
                        rect.Y += args.Location.Y - initialLocation.Y;
                        AbsoluteLayout.SetLayoutBounds(viewrect);

                        // Update all link positions of the touched node
                        // The positions depend on the positions of the start and end node
                        for (int i = 0i < NoN[page]; i++){
                            if(link[page,nodeID,i].exist){
                                if (node[pagenodeID].X + node[pagenodeID].Width < node[pagei].X)
                                {
                                    link[pagenodeIDi].x1 = (int)((node[pagenodeID].X + node[pagenodeID].Width) * scale);
                                    link[pagenodeIDi].y1 = (int)((node[pagenodeID].Y + node[pagenodeID].Height / 2 + space) * scale);
                                    link[pagenodeIDi].x2 = (int)(node[pagei].X * scale);
                                    link[pagenodeIDi].y2 = (int)((node[pagei].Y + node[pagei].Height / 2 + space) * scale);
                                }
                                else if (node[pagei].X + node[pagei].Width < node[pagenodeID].X)
                                {
                                    link[pagenodeIDi].x1 = (int)(node[pagenodeID].X * scale);
                                    link[pagenodeIDi].y1 = (int)((node[pagenodeID].Y + node[pagenodeID].Height / 2 + space) * scale);
                                    link[pagenodeIDi].x2 = (int)((node[pagei].X + node[pagei].Width) * scale);
                                    link[pagenodeIDi].y2 = (int)((node[pagei].Y + node[pagei].Height / 2 +space) * scale);
                                }
                                else if (node[pagenodeID].Y < node[pagei].Y + node[pagei].Height)
                                {
                                    link[pagenodeIDi].x1 = (int)((node[pagenodeID].X + node[pagenodeID].Width / 2 + space) * scale);
                                    link[pagenodeIDi].y1 = (int)((node[pagenodeID].Y + node[pagenodeID].Height) * scale);
                                    link[pagenodeIDi].x2 = (int)((node[pagei].X + node[pagei].Width / 2 + space) * scale);
                                    link[pagenodeIDi].y2 = (int)((node[pagei].Y) * scale);
                                }
                                else
                                {
                                    link[pagenodeIDi].x1 = (int)((node[pagenodeID].X + node[pagenodeID].Width / 2 + space) * scale);
                                    link[pagenodeIDi].y1 = (int)((node[pagenodeID].Y) * scale);
                                    link[pagenodeIDi].x2 = (int)((node[pagei].X + node[pagei].Width / 2 + space) * scale);
                                    link[pagenodeIDi].y2 = (int)((node[pagei].Y + node[pagei].Height) * scale);
                                }

                            }
                            if (link[pageinodeID].exist)
                            {
                                if (node[pagei].X + node[pagei].Width < node[pagenodeID].X )
                                {
                                    link[pageinodeID].x1 = (int)((node[pagei].X + node[pagei].Width) * scale);
                                    link[pageinodeID].y1 = (int)((node[pagei].Y + node[pagenodeID].Height / 2 - space) * scale);
                                    link[pageinodeID].x2 = (int)(node[pagenodeID].X * scale);
                                    link[pageinodeID].y2 = (int)((node[pagenodeID].Y + node[pagenodeID].Height / 2 -space) * scale);
                                }
                                else if(node[pagenodeID].X + node[pagenodeID].Width < node[pagei].X)
                                {
                                    link[pageinodeID].x1 = (int)(node[pagei].X * scale);
                                    link[pageinodeID].y1 = (int)((node[pagei].Y + node[pagei].Height / 2 - space) * scale);
                                    link[pageinodeID].x2 = (int)((node[pagenodeID].X + node[pagenodeID].Width) * scale);
                                    link[pageinodeID].y2 = (int)((node[pagenodeID].Y + node[pagenodeID].Height / 2 - space) * scale);
                                }
                                else if(node[pagei].Y < node[pagenodeID].Y+node[pagenodeID].Height)
                                {
                                    link[pageinodeID].x1 = (int)((node[pagei].X + node[pagei].Width / 2 - space) * scale);
                                    link[pageinodeID].y1 = (int)((node[pagei].Y + node[pagei].Height ) * scale);
                                    link[pageinodeID].x2 = (int)((node[pagenodeID].X + node[pagenodeID].Width / 2 -space) * scale);
                                    link[pageinodeID].y2 = (int)((node[pagenodeID].Y ) * scale);
                                }
                                else
                                {
                                    link[pageinodeID].x1 = (int)((node[pagei].X + node[pagei].Width / 2 - space) * scale);
                                    link[pageinodeID].y1 = (int)((node[pagei].Y) * scale);
                                    link[pageinodeID].x2 = (int)((node[pagenodeID].X + node[pagenodeID].Width / 2 - space) * scale);
                                    link[pageinodeID].y2 = (int)((node[pagenodeID].Y + node[pagenodeID].Height ) * scale);
                                }
                            }
                        }
                        canvas.InvalidateSurface();
                    }
                    break;

                case TouchActionType.Released:
                    if (dragDictionary.ContainsKey(view) && dragDictionary[view].Id == args.Id)
                    {
                        dragDictionary.Remove(view);
                        canvas.InvalidateSurface();
                    }
                    break;
            }
        }


        // draw links using SkiaSharp
        void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs e)
        {
            var surface = e.Surface;
            var canvas = surface.Canvas;

            canvas.Clear();

            // Draw Line
            for (int i = 0i < NoN[page]; i++)
            {
                for (int j = 0j < NoN[page]; j++)
                {
                    if (link[pageij].exist)
                    {
                        canvas.DrawLine(link[pageij].x1,
                                        link[pageij].y1,
                                        link[pageij].x2,
                                        link[pageij].y2linePaint);
                        // draw circle instead of arrow at the ending point of this link
                        canvas.DrawCircle(link[pageij].x2,
                                          link[pageij].y2,
                                          5 * scale,
                                          circlePaint);
                        // draw link title
                        canvas.DrawText(linkTitle[pageij],
                                        (link[pageij].x1 + link[pageij].x2) / 2,
                                        (link[pageij].y1 + link[pageij].y2) / 2,
                                        textPaint);
                    }
                }
            }
        }
        private SKPaint linePaint = new SKPaint
        {
            StrokeWidth = 2,
            IsAntialias = true,
            Color = SKColors.Black
        };
        private SKPaint circlePaint = new SKPaint
        {
            Color = SKColors.Black
        };
        private SKPaint textPaint = new SKPaint
        {
            TextSize = 15,
            Color = SKColors.Black
        };
    }
}

In the first some lines of this code, two classes and several variables are declared.  "Page" class has two fields, characters group name and characters, and this class will be tap-enable items of PageListView in the main page.  "Link" class is for directed links connecting nodes of characters explained later, and has five fields, link existence and coordinates of start and end points.  To share some sort of data between methods, declare some array data and initialize them.  maxpage and maxnode are the variables to set the size of the array data.  BTW, in the declaration of the variables, "int maxpage=20;" causes a built error.  "static int maxpage=20;" seems to be correct.  I seem to need to learn more...

When text input of characters group name occurred in the main page, AddPage method are called.  Firstly, the characters group name are added to PageListView, then, create page contents to create and display the characters correlation diagram.  The page contains Entries and Buttons to input characters and link titles, Buttons for page navigation, canvas on which links are drawn using SkiaSharp, and AbsoluteLayout on which some Entries displaying characters are placed.  The canvas and the AbsoluteLayout are overlapped in a Grid in the same way as the SkiaSharp sample program.  Finally, save some variables referenced from other methods.  As you have noticed, SkiaSharp and SkiaSharp.Views.Forms of NuGet packages should be added to each projects.


JumePage method detects the list index of the tapped characters group name using IndexOf method, and navigates to the page of the tapped characters group.

AddNodeEntryTextChanged and the some following methods are to add characters node and links.  I couldn't find a proper way to get text from Entry control described in not xaml but code behind (C#), so I used TextChanged event to do it this time.  Is there any other smart way?

MovePreviousPage and the following two methods are for page navigation.

AddNodeBox method adds and displays characters as nodes of characters correlation diagram.  The characters are displayed using Entry instead of Label so that they could be edited in the future, but they don't need to be edited in this version, so made InputTransparent property of the Entry "true."  The Entry controls are not directly placed in absoluteLayout.  Once they are placed in ContentViews, and the ContentViews are placed in absoluteLayout, so that they could be tapped and dragged by OnTouchEffectAction explained later.  TapGestureRecognizer detects tap event when connecting two characters (nodes).  After AddLink Button is pressed, a link is created so that the 1st tapped node is start point and the 2nd one is the end point.  

OnTouchEffectAction method detects touch action type against characters nodes such as "Pressed", "Moved", and "Released."  While drag a node linked with other node, calculate the start and end points of the link optimally according to the relative position of the linked two nodes.  And also, any two nodes can be linked in both directions, and the two links are placed with a small gap not to overlapp each other.

Finally, in OnCanvasViewPaintSurface, draw links, end point circles of the links, and link titles at the each positions calculated in OnTouchEffectAction method.  I actually wanted to draw arrow heads at the end point of links, but I didn't because the codes becomes complicated.  Also, for the positions of link titles, it may be better to calculate the optimum positions, but I didn't do it, since it has nothing to do with this theme, study drawing and tap&drag.

In App.xaml.cs, just changed "MainPage=new MainPage();" to "MainPage=new NavigationPage(new MainPage());"
TouchTracking.cs
using System;
using Xamarin.Forms;


namespace TouchTracking
{
    class DragInfo
    {
        public DragInfo(long id, Point pressPoint)
        {
            Id = id;
            PressPoint = pressPoint;
        }

        public long Id { private setget; }

        public Point PressPoint { private setget; }
    }

    public delegate void TouchActionEventHandler(object senderTouchActionEventArgs args);

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

    public class TouchEffect : RoutingEffect
    {
        public event TouchActionEventHandler TouchAction;

        public TouchEffect() : base("XamarinDocs.TouchEffect")
        {
        }

        public bool Capture { setget; }

        public void OnTouchAction(Element elementTouchActionEventArgs args)
        {
            TouchAction?.Invoke(elementargs);
        }
    }

    public class TouchActionEventArgs : EventArgs
    {
        public TouchActionEventArgs(long idTouchActionType type, Point locationbool isInContact)
        {
            Id = id;
            Type = type;
            Location = location;
            IsInContact = isInContact;
        }

        public long Id { private setget; }

        public TouchActionType Type { private setget; }

        public Point Location { private setget; }

        public bool IsInContact { private setget; }
    }
}

This code should be added to PCL.  This is just a part of SkiaSharp official sample program (demo program tapping and dragging bitmap image of monkey).
And also, some codes for each device platform are necessary.  In the case of iOS, TouchEffect.cs and TouchRecognizer.cs should be added to iOS project.  You can find these codes in the official sample program.

Ran this sample program on iOS simulator:



Main page.  The three characters groups are listed, and the fourth characters group are being inputted in the Entry.  If you tap one of the characters group, "Anti Terrorist Task Force" for example, move to the page shown below.

The four characters has been added, and two of them are just linked (They are characters from Nelson Demille's "The Lion's Game").  Input "colleague" into Link Name Entry, and press AddLink Button -> tap "John Corey" node -> tap "George Foster" node, then you can see the display shown above.  Every node can be dragged, and the link positions change dynamically according to the node positions.



All characters (nodes) are linked each other.

As mentioned above, I studied drawing using SkiaSharp and how to tap&drag items.





コメント

このブログの人気の投稿

Get the Color Code from an Image Pixel

PCL Storage (1)

Prolog Interpreter