XamarinForm Effects 调用事件
在Xamarin.Forms控件中实现底层多点触控跟踪。
一个effect可以定义和调用一个事件,在底层本地视图中发出信号的变化。这篇文章演示如何实现底层多点触控跟踪,以及如何生成信号触摸活动的事件。
本文描述的Effect提供了对底层触摸事件的访问。这些低级事件在现有的GestureRecognizer类中是不可用的,但是它们对于某些类型的应用程序来说是非常重要的。例如,手指画画应用程序需要跟踪单个手指在屏幕上移动的情况。音乐键盘应用程序需要检测每个按键上的点击和释放,以及一个手指从一个键滑动到另一个键的滑音。
Effect是一个理想多点触控跟踪的,因为它可以附加到任何一个Xamarin.Forms元素上。
平台触摸事件
iOS、Android和通用的Windows平台都包含一个底层API,它允许应用程序检测触摸活动。这些平台都能区分三种基础触摸事件类型:
- Pressed 当一个手指触摸到屏幕时。
- Moved 当一个手指触摸到屏幕移动时。
- Released 当一个手指从屏幕上释放时。
在多点触控环境中,同一时间可以有多个手指触摸屏幕。各种平台包含一个识别(ID)号,应用程序可以用来区分多个手指。
在iOS中,UIView类定义了三个可覆盖的方法,TouchesBegan,TouchesMoved和TouchesEnded
来对应这三个事件。文章多点触控跟踪描写了如何使用这些方法。但是,iOS程序不需要覆盖从UIView派生的类来使用这些方法。iOSUIGestureRecognizer
也定义了这三个方法,并且你可以附加一个类的实例,它从UIGestureRecognizer派生到任何UIView对象。
在Android中,View类定义了一个可覆盖的OnTouchEvent
方法去处理所有的触摸活动。这里触摸活动类型定义为枚举类型Down、PointerDown、Move、Up和PointerUp,描述在文章多点触摸跟踪中。Android View也定义了名为Touch的事件,他允许一个事件handler附加到任何View对象上。
在通用Windows平台(UWP)中,UIElement类定义了名为PointerPressed,PointerMoved和PointerReleased的事件。在文章Handle Pointer Input article on MSDN和UIElement类的API文档中描写了这些事件。
通用Windows平台中的Pointer
(指针)API旨在统一鼠标、触摸和笔输入。因此,当鼠标移动到一个元素上时,即使鼠标按钮没有被抑制,PointerMoved
事件也会被调用。与这些事件关联的PointerRoutedEvent-Args
对象有一个名为Pointer
的属性,这个属性有一个名为IsInContact
的属性,该属性指示是按下鼠标按钮还是与屏幕进行接触。
此外,UWP定义两个名为PointerEntered
和PointerExited的鼠标事件。这些指示当鼠标或手指从一个元素移动到其他元素。例如,考虑两个相邻的元素A和B。这两个元素都为指针事件安装了处理程序。当一个手指按压A时,PointerPressed
事件被触发,当手指移动时,A调用PointerMoved事件。如果手指从A移动到B,A触发一个PointerExited事件,B触发一个PointerEntered
事件。如果指被释放,B调用一个pointerrelease事件。
iOS和Android平台不同于UWP:当手指触摸到视图时,第一个调用TouchesBegan或OnTouchEvent的视图继续得到所有的触摸活动,即使手指移动到不同的视图。如果应用程序捕捉到指针,UWP的行为类似:在pointerentry事件处理程序中,元素调用CapturePointer,然后从该手指获取所有的触摸活动。
UWP方法对某些类型的应用程序非常有用,例如,音乐键盘。每个键都可以处理该键的触摸事件,并且使用pointerenter和PointerExited 事件
检测当一个手指从一个键滑到另一个键。
因此,本文描述的触摸跟踪效果实现了UWP方法。
触摸跟踪Effect API
Touch Tracking Effect Demos示例包含实现底层触摸跟踪的类(和枚举)。这些类型属于命名空间TouchTracking
,并都以单词Touch开始。TouchTrackingEffectDemos便携式类库项目包括触摸事件类型的TouchActionType枚举:
public enum TouchActionType
{
Entered,
Pressed,
Moved,
Released,
Exited,
Cancelled
}
所有平台还包含一个指示触摸事件已被取消的事件。
TouchEffect
类在PCL源自于RoutingEffect
,并定义了一个名为TouchAction
的时间和一个名为OnTouchAction
的方法,该方法用来调用TouchAction
事件。
public class TouchEffect : RoutingEffect
{
public event TouchActionEventHandler TouchAction; public TouchEffect() : base("XamarinDocs.TouchEffect")
{
} public bool Capture { set; get; } public void OnTouchAction(Element element, TouchActionEventArgs args)
{
TouchAction?.Invoke(element, args);
}
}
应用程序可以使用Id属性跟踪单个手指。通知IsInContact
属性。这个属性永远是Pressed
(按压)事件为true
,Released
事件为false
。也总是在iOS和Android上Moved
(移动)事件为true
。在UWP上,当程序运行在桌面鼠标指针移动时没有按下按钮时,IsInContact
属性Moved
(移动)事件为可能为false
。
你可以在自己的应用程序中使用TouchEffect
类到,包括解决方案的PCL项目中的文件,并添加一个实例到任何Xamarin.Froms元素的Effects集合中。附加一个处理程序到TouchAction
事件已获得触摸事件。
在你自己的应用程序中使用TouchEffect
,你还需要在TouchTrackingEffectDemos解决方案中包含平台的实现。
触摸跟踪Effect实现
iOS、Android和UWP对TouchEffect
实现的描写在下面,首先是简单的实现(UWP),最后是iOS的实现,因为iOS的实现比其他的更加复杂。
UWP实现
UWP实现TouchEffect
是简单的,类继承PlatformEffect
并且包含两个装配属性:
[assembly: ResolutionGroupName("XamarinDocs")]
[assembly: ExportEffect(typeof(TouchTracking.UWP.TouchEffect), "TouchEffect")] namespace TouchTracking.UWP
{
public class TouchEffect : PlatformEffect
{
...
}
}
覆盖OnAttached
将一些信息保存为并将处理程序附加到所有指针事件:
public class TouchEffect : PlatformEffect
{
FrameworkElement frameworkElement;
TouchTracking.TouchEffect effect;
Action<Element, TouchActionEventArgs> onTouchAction; protected override void OnAttached()
{
// 获取与该效果附加到的元素对应的Windows FrameworkElement
frameworkElement = Control == null ? Container : Control; // 获取PCL中的 TouchEffect 类
effect = (TouchTracking.TouchEffect)Element.Effects.
FirstOrDefault(e => e is TouchTracking.TouchEffect); if (effect != null && frameworkElement != null)
{
// 保存方法,以调用触摸事件
onTouchAction = effect.OnTouchAction; // 在FrameworkElement上设置事件处理程序
frameworkElement.PointerEntered += OnPointerEntered;
frameworkElement.PointerPressed += OnPointerPressed;
frameworkElement.PointerMoved += OnPointerMoved;
frameworkElement.PointerReleased += OnPointerReleased;
frameworkElement.PointerExited += OnPointerExited;
frameworkElement.PointerCanceled += OnPointerCancelled;
}
}
...
}
OnPointerPressed
处理程序通过调用CommonHandler
方法中的onTouchAction
字段来调用效果事件:
public class TouchEffect : PlatformEffect
{
...
void OnPointerPressed(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Pressed, args); // 检查捕获属性的设置。
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也会检查PCL effect类中Capture
属性的值,如果值为true,则调用CapturePointer
。
其他UWP事件处理程序更简单:
public class TouchEffect : PlatformEffect
{
...
void OnPointerEntered(object sender, PointerRoutedEventArgs args)
{
CommonHandler(sender, TouchActionType.Entered, args);
}
...
}
Android实现
Android和iOS实现必然更复杂,因为当一个手指从一个元素移动到其他元素是,他们必须实现Exited
和
事件。这两个实现的结构类似。Entered
AndroidTouchEffect
类添加一个
事件的处理程序:Touch
view = Control == null ? Container : Control;
...
view.Touch += OnTouch;
TouchEffect
类还要定义两个静态的字典:
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>();
...
每次调用
覆盖时,OnAttached
viewDictionary
都会获取一个新的entry
viewDictionary.Add(view, this);
在OnDetached中将entry从字典中删除。每个TouchEffect
的实例都与一个特定的视图关联,这个视图的effect
是附加的。静态的字典允许任何TouchEffect
的实现去枚举所有其他视图和他们对于的
实现。这是允许将事件从一个视图转移到另一个视图的必要条件。TouchEffect
Android
分配一个ID code
到触摸事件,为了允许应用程序跟踪单个手指。idToEffectDictionary
将这个ID code
与
示例关联起来。TouchEffect
当手指按压
处理程序被调用时,一个项被添加到字典中:Touch
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 = pclTouchEffect.Capture;
break;
当手指从屏幕中释放时,项从
中删除,idToEffectDictionary
方法只收集调用FireEvent
方法所需的所有信息:OnTouchAction
void FireEvent(TouchEffect touchEffect, int id, TouchActionType actionType, Point pointerLocation, bool isInContact)
{
// 获取调用触发事件的方法。
Action<Element, TouchActionEventArgs> onTouchAction = touchEffect.pclTouchEffect.OnTouchAction; // 获取视图中指针的位置。
touchEffect.view.GetLocationOnScreen(twoIntArray);
double x = pointerLocation.X - twoIntArray[0];
double y = pointerLocation.Y - twoIntArray[1];
Point point = new Point(fromPixels(x), fromPixels(y)); // 调用方法
onTouchAction(touchEffect.formsElement,
new TouchActionEventArgs(id, actionType, point, isInContact));
}
所有其他触摸类型都以两种不同的方式处理:如果Capture
属性为true,
触摸事件可以直接的简单转化为
信息。当TouchEffect
属性为Capture
信息获取更加困难,因为触摸事件可能需要从一个视图移动到其他视图。这是false,
TouchEffect
方法的职责,它在移动事件中被调用。这个方法使用两个静态字典。他通过枚举CheckForBoundaryHop
判断手指当前触摸的视图,并且使用viewDictionary
存储现在的idToEffectDictionary
实现(和现在的视图)关联到一个独有的TouchEffect
ID:
void CheckForBoundaryHop(int id, Point pointerLocation)
{
TouchEffect touchEffectHit = null; foreach (Android.Views.View view in viewDictionary.Keys)
{
// 获取视图矩形
try
{
view.GetLocationOnScreen(twoIntArray);
}
catch // System.ObjectDisposedException: 无法访问已处理的对象。
{
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;
}
}
如果idToEffectionDictionary
有更新,方法可能调用
为了FireEvent
和Exited
从一个视图转移到另一个视图。然而,手指可能被移动到一个没有附加Entered
的视图区域,或者从该区域移动到带有附加TouchEffect
的视图。TouchEffect
当视图被存取时注意try
和catch
代码块。在导航页面,导航回主界面时,
方法是没有被调用的,并且项保留在OnDetached
中,但是viewDictionary
认为他们已被处理。Android
iOS实现
iOS
实现与Android
实现类似,只是iOS
类必须实例化一个TouchEffect
的派生类。这是一个在UIGestureRecognizer
项目名为iOS
的类。这个类维持两个静态的字典,用来存储TouchRecognizer
的实例:TouchRecognizer
static Dictionary<UIView, TouchRecognizer> viewDictionary =
new Dictionary<UIView, TouchRecognizer>(); static Dictionary<long, TouchRecognizer> idToTouchDictionary =
new Dictionary<long, TouchRecognizer>();
这个
类的结构类似于TouchRecognizer
的Android
类。TouchEffect
让触摸效果发挥作用
TouchTrackingEffectDemos程序包含5个页面,他们用来测试常见的触摸跟踪效果。
BoxView Dragging页面运行你去添加BoxView
元素到一个
然后在屏幕上拖拽他们。AbsoluteLayout,
实例化两个XAML file
按钮分别添加Button
元素到BoxView
或清空AbsoluteLayout,
AbsoluteLayout。
code-behind file中的方法添加一个新的BoxView
到AbsoluteLayout
,并且将一个TouchEffect
对象添加到BoxView
,并将一个事件处理程序附加到这个效果:
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);
}
TouchAction事件处理程序处理所有的BoxView元素的所有触摸事件,但它需要谨慎行事:它无法运行两个手指在一个BoxView上,因为程序只实现拖拽,并且两个手指会相互干扰。因此,该页面为当前被跟踪的每个手指定义了一个嵌入式类:
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>();
dragDictionary包含当前被拖动的每个BoxView的条目。
Pressed触摸动作添加一个项到字典,在
Released动作移除改项。
Pressed的逻辑必须检查字典中是否已经有一个条目用于那个BoxView。如果存在,
BoxView已经开始拖动,并且这个新事件是同一BoxView的第二根手指。对于
Moved和
Released的操作,事件处理程序必须检查字典是否为该BoxView提供了一个条目,并且那个拖动的BoxView的touch Id属性与字典条目中的一个条目相匹配:
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
BoxView boxView = sender as BoxView; switch (args.Type)
{
case TouchActionType.Pressed:
// 在已经触摸的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;
}
}
Pressed
逻辑将
对象的TouchEffect
属性设置为Capture(捕获)
。这可以将所有后续事件交付给同一个事件处理程序。true
Moved
逻辑通过变更
的附加属性来移动LayoutBounds
。事件参数的BoxView
属性总是相对于被拖拽的Location
而言,如果BoxView
被一个恒定的速率拖拽,连贯事件的BoxView
属性将会大致相同。例如,如果一个手指在Location
中心按压,BoxView
操作保存一个Pressed
的属性,对于后续事件来说,这仍然是相同的。如果PressPoint(50,50)
是已恒定的熟虑拖拽对角线,后来的BoxView
属性在Location
操作时,它的值应该是Moved
,在这种情况下,移动的逻辑在(55,55)
的水平和垂直位置增加了BoxView
。这移动了5
,使它的中心再次直接在手指下面。BoxView
您可以使用不同的手指同时移动多个BoxView元素。
子类视图
通常Xamarin.Forms元素容易处理自己的触摸事件。Draggable BoxView Dragging页的功能与BoxView Dragging页的相同,但是用户拖拽的元素是来自BoxView的DraggableBoxView类的实例:
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;
}
}
}
当对象是第一次初始化时,创建并附加TouchEffect,并且设置
Capture属性。不需要字典,因为这个类他自己存储了与每个手指相关的isBeingDragged、pressPoint和
touchId的值。Moved处理改变
TranslationX和
TranslationY属性,因此即使DraggableBoxView的父元素不是AbsoluteLayout,逻辑也会起作用。
结合SkiaSharp
下面两个示范需要graphics(制图),并且为了这个目的使用了SkiaSharp。在你学些这些实例前,你可能需要学习一些Using SkiaSharp in Xamarin.Forms。前面两篇文章("SkiaSharp Drawing Basics" 和"SkiaSharp Lines and Paths")包含你需要的任何东西。
Ellipse Drawing页允许你使用手指在屏幕上画一个椭圆。依赖你如何移动你的手指,你可以从左上到右下画椭圆,或从任何一个地方到其他地方。使用随机颜色和不透明度绘制椭圆。
然后如果你触摸一个椭圆,你可以拖拽他到其他地方。这需要一种称为“hit-testing”的技术,它涉及在特定的点上搜索图形对象。SkiaSharp椭圆不是Xamarin.Forms元素,所以他们不能执行我们的
TouchEffect处理。TouchEffect必须应用于整个SKCanvasView对象。
EllipseDrawPage.xaml文件在一个single-cell Grid中实例化
SKCanvasView。
TouchEffect对象附加到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>
在Android和UWP中
TouchEffect可以直接附加到SKCanvasView上,但是在iOS上
TouchEffect不能工作。注意Capture是设置为true。
SkiaSharp渲染的每个椭圆都由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; } // 拖拽操作
public Point LastFingerLocation { set; get; } // 拖拽测试
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和
EndPoint属性被使用;在椭圆拖拽时
Rectangle属性被使用。
当椭圆开始拖拽时LastFingerLocation属性发挥作用,并且
IsInEllipse方法做测试。如果指向是内部椭圆,该方法返回true。
code-behind file维护三个集合:
Dictionary<long, EllipseDrawingFigure> inProgressFigures = new Dictionary<long, EllipseDrawingFigure>();
List<EllipseDrawingFigure> completedFigures = new List<EllipseDrawingFigure>();
Dictionary<long, EllipseDrawingFigure> draggingFigures = new Dictionary<long, EllipseDrawingFigure>();
draggingFigure字典包含一个
completedFigures集合的子集。SkiaSharp的
PaintSurface事件处理程序简单渲染
completedFigures、
对象: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);
}
}
触摸处理中最棘手的部分是Pressed
的处理。这是hit-testing处理的地方,但是如果代码发现用户手指下的椭圆,那么椭圆只能被拖拽,如果它没有还没有被另外的手指拖拽。如果用户手指下没有椭圆,那么代码开始处理绘画一个新的椭圆:
case TouchActionType.Pressed:
bool isDragOperation = false; // 循环已完成的图形
foreach (EllipseDrawingFigure fig in completedFigures.Reverse<EllipseDrawingFigure>())
{
// 检查手指是否碰到了一个椭圆
if (fig.IsInEllipse(ConvertToPixel(args.Location)))
{
// 暂时假定这是一个拖动操作。
isDragOperation = true; // 循环所有当前开始拖拽的手指
foreach (EllipseDrawingFigure draggedFigure in draggingFigures.Values)
{
// 如果这里匹配, 我们需要挖掘更深
if (fig == draggedFigure)
{
isDragOperation = false;
break;
}
} if (isDragOperation)
{
fig.LastFingerLocation = args.Location;
draggingFigures.Add(args.Id, fig);
break;
}
}
} if (isDragOperation)
{
// 将拖动的椭圆移动到completedFigures 的末尾,这样它就会被绘制在顶部
EllipseDrawingFigure fig = draggingFigures[args.Id];
completedFigures.Remove(fig);
completedFigures.Add(fig);
}
else // 开始创建一个新的椭圆
{
// 产生随机byte为了随机颜色
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;
Finger Paint页是SkiaSharp的其他示例,你可以从两个选择器视图中选择一个笔划颜色和笔画宽度,然后用一个或多个手指绘制:
这个示例也需要一个单独的类来表示屏幕上绘制的每一行:
class FingerPaintPolyline
{
public FingerPaintPolyline()
{
Path = new SKPath();
} public SKPath Path { set; get; } public Color StrokeColor { set; get; } public float StrokeWidth { set; get; }
}
SKPath对象渲染每条线。FingerPaint.xaml.cs文件维护这些对象的两个集合,一种是目前正在绘制的折线,另一种是已完成的折线:
Dictionary<long, FingerPaintPolyline> inProgressPolylines = new Dictionary<long, FingerPaintPolyline>();
List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();
Pressed处理创建一个新的FingerPaintPolyline,调用在path对象上
MoveTo去存储初始点,并且添加哪个对象到inProgressPolylines字典中。
Moved处理用新的手指位置调用path对象上的
LineTo,而
Released处理将以完成的polyline从
inProgressPolylines转移到completedPolylines。再一次,实际的SkiaSharp绘图代码相对简单:
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);
}
}
跟踪视图到视图的触摸
之前所有的实例都为了TouchEffect将
Capture属性设置为true,当
TouchEffect被创建时或
Pressed事件被触发时。确保相同的元素接收第一个按下视图的手指所关联的所有事件。最后一个示例没有将
Capture设置为true。这是因为当手指接触屏幕从一个元素到其他元素时行为是不一样的。手指移动的元素从接收到一个Type属性设置到
TouchActionType.Exited
,第二个元素接收一个带有TouchActionType.Entered的Type
设置的事件。
这种类型的触摸处理对音乐键盘非常有用。一个键应该能够在被按压的时候检测到,而且当手指从一个键滑到另一个键时也能检测到。
Silent Keyboard界面定义了少量的WhiteKey
和BlackKey
类,这些是源自BoxView的Key
。
Key类类已经准备好用于实际的音乐程序。它定义公共的IsPressed和KeyNumber
属性,这将被设置为MIDI标准所建立的关键代码。Key类也定义了名为StatusChanged的事件,当IsPressed属性被更改时调用。
每个键上允许有多个手指。为此,Key类维护了当前触摸该键的所有手指touch ID的List。
List<long> ids = new List<long>();
TouchAction 事件处理程序为
Pressed(释放)事件类型和
ID,但是只有当Entered(退出)事件类型在ids列表中添加
IsInContact属性为true时才为Entered事件添加。ID是用来从List中移除
Released(释放)和
Exited(退出)事件:
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;
}
}
AddToList和RemoveFromList方法都检查法都检查List是否在空和非空之间进行了更改,如果是,则调用StatusChanged事件。
XAML file页面中设置了各种白键和黑键元素,当手机处于横向模式时,效果最好:
如果你把手指划过这些键,你会看到,触摸事件从一个键转移到另一个键的颜色的细微变化。
总结
本文演示了如何在效果中调用事件,以及如何编写和使用实现低级多点触摸处理的效果。
XamarinForm Effects 调用事件的更多相关文章
- Vue父组件与子组件传递事件/调用事件
1.Vue父组件向子组件传递事件/调用事件 <div id="app"> <hello list="list" ref="child ...
- c# 开发ActiveX控件,添加事件,QT调用事件
c# 开发 ActiveX 的过程参考我的另一篇文章 : https://www.cnblogs.com/baqifanye/p/10414004.html 本篇讲如何 在C# 开发的ActiveX ...
- PowerBuilder学习笔记之调用事件和函数
2.7.1调用事件和函数 完整语法:[ObjectName]ancestorclass::[type][when]name([argumnetlist]) 说明:ObjectName:指定函数或事件的 ...
- C#中怎样跨窗体调用事件-从事件订阅实例入手
场景 C#中委托与事件的使用-以Winform中跨窗体传值为例: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100150700 ...
- [原创] delphi KeyUp、KeyPress、Keydown区别和用法,如何不按键盘调用事件
KeyPress (Sender: TObject; var Key: Char); 当用户按下键盘上的字符键(字母,数字) 会触发该事件,功能键则不会(F1-F12,Ctrl,Alt,Shift ...
- 从ajax获取的数据无法通过Jquery选择器来调用事件
如果标签是动态生成的,比如说div.tr.td等,若需通过Jquery来获取事件,那么需要用live来绑定相应的事件. 比如说绑定div的click事件 $("div").live ...
- vue 父组件向子组件传递事件/调用事件
方法一:子组件监听父组件发送的方法 方法二:父组件调用子组件方法 子组件: export default { mounted: function () { this.$nextTick(functio ...
- 【学徒日记】Unity 动画调用事件
http://note.youdao.com/noteshare?id=a15f965fc57a0b25c87ee09388cf0f4a 具体内容看上面的链接. 1. 在脚本里写一个函数,它的参数只能 ...
- CAD对象的夹点被编辑完成后调用事件(com接口VB语言)
主要用到函数说明: _DMxDrawXEvents::ObjectGripEdit 对象的夹点被编辑完成后,会调用该事件,详细说明如下: 参数 说明 LONGLONG lId 对象的id LONG i ...
随机推荐
- Developing User Interfaces
Developing a User Interface with ADF Faces Purpose This tutorial covers developing the user interf ...
- redis注册成window服务
注册服务 redis-server.exe –service-install redis.windows.conf 删除服务 redis-server –service-uninstall 开启服务 ...
- HBase事务
众所周知,ACID是指原子性(Atomicity),一致性(Consistency),隔离性(Isolation)和持久性(Durability). HBase对同一行数据的操作提供ACID保证.HB ...
- HBase丢失数据的故障和原因分析
hbase的稳定性是近期社区的重要关注点,毕竟稳定的系统才能被推广开来,这里有几次稳定性故障和大家分享. 第一次生产故障的现象及原因 现象: 1 hbase发现无法写入 2 通过hbc ...
- SharePoint 2007 图片库视图不可用、页面标题不显示
描述: 问题1:SharePoint新建图片库,想选择"视图"-"所有图片",选择"详细信息.幻灯片.缩略图"等视图,均没有反应.如图1. ...
- shell中关于sort的-o选项
sort -o选项意思为将排序后的结果写入文件,但你可能会说我可以重定向啊: sort >file 但如果你要排序文件names里的行再写回排序后的结果: sort names > nam ...
- Yii2.0源码阅读-PHP如何与redis通信?
PHP与Redis可以通过socket进行通信,前提是PHP需要实现Redis的协议 RESP协议描述: 字符串 \r\n : 表示一个正确的状态信息,具体信息是'+'后面的字符(Simple Str ...
- MOOS通配符订阅
MOOS通配符订阅 简介 通配符订阅是MOOSV10的重要进步,客户端可以通过此方式订阅名字和来源符合简单正则表达式的数据. 现在仅支持"*"和"?"两种通配符 ...
- RunTime运行时在iOS中的应用之UITextField占位符placeholder
RunTime运行时机制 runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API. 在我们平时编写的Objective-C代码中, 程序运行过程时, 其实最终 ...
- nodejs+express blog项目分享
项目简介:项目采用nodejs+express+typescript+mongodb技术搭建 主要功能: 1.用户注册 2.用户登录 3.文章管理模块 4.图片管理模块 5.token认证 6.密码加 ...