android views 1.0.5-alpha - separating out GL and Canvas View for android too

This commit is contained in:
Wouter Steenbergen 2021-10-15 12:56:23 -07:00
Родитель 0f8e922655
Коммит a0bfed9ec3
12 изменённых файлов: 1146 добавлений и 8 удалений

Просмотреть файл

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FFMediaToolkit" Version="4.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FluidSharp\FluidSharp.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,118 @@
using FFMediaToolkit;
using FFMediaToolkit.Encoding;
using FFMediaToolkit.Graphics;
using FluidSharp.Layouts;
using FluidSharp.State;
using FluidSharp.Widgets;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace FluidSharp.Video.Recorder
{
public class VideoRecorderLayoutSurface : LayoutSurface
{
public CancellationTokenSource CancellationTokenSource;
public VideoRecorderLayoutSurface(Device device, MeasureCache measureCache, string FFmpegPath) : base(device, measureCache, null, new VisualState(null, null))
{
if (FFmpegLoader.FFmpegPath != FFmpegPath)
FFmpegLoader.FFmpegPath = FFmpegPath;// @"C:\util\ffmpeg-4.2.1-win-64\bin";
}
public void Stop()
{
CancellationTokenSource.Cancel();
}
public async Task Run(string fullfilename, SKSize size, float scale, int fps, Func<Widget> makeWidget)
{
var MSBetweenFrame = 1000f / fps;
var w = (((int)(size.Width * scale)) / 4) * 4;
var h = (((int)(size.Height * scale)) / 4) * 4;
var framesize = new System.Drawing.Size(w, h);
var settings = new VideoEncoderSettings(width: w, height: h, framerate: fps, codec: VideoCodec.H264);
settings.EncoderPreset = EncoderPreset.Fast;
settings.CRF = 17;
CancellationTokenSource = new CancellationTokenSource();
var framelen = TimeSpan.FromMilliseconds(MSBetweenFrame);
var tframe = DateTime.Now;
var frameid = 0;
using (var outfile = MediaBuilder.CreateContainer(fullfilename).WithVideo(settings).Create())
{
while (!CancellationTokenSource.IsCancellationRequested)
{
DrawFrame(outfile, makeWidget, framesize, scale);
tframe = tframe.Add(framelen);
var twait = tframe.Subtract(DateTime.Now);
if (twait.TotalMilliseconds > 0)
{
System.Diagnostics.Debug.WriteLine($"waiting {twait.TotalMilliseconds} ms");
await Task.Delay(twait);
}
else
{
System.Diagnostics.Debug.WriteLine($"WARNING: producing frame took more than {MSBetweenFrame} ms");
}
frameid++;
}
}
}
private unsafe void DrawFrame(MediaOutput outfile, Func<Widget> makeWidget, System.Drawing.Size framesize, float scale)
{
var widget = makeWidget();
widget = new Scale(scale, widget);
using (var bitmap = new SKBitmap(new SKImageInfo((int)framesize.Width, (int)framesize.Height, SKImageInfo.PlatformColorType, SKAlphaType.Premul)))
using (var canvas = new SKCanvas(bitmap))
{
//surface.Canvas.Clear(SKColors.Transparent);
canvas.Clear(SKColors.White);
SetCanvas(canvas);
Paint(widget, new SKRect(0, 0, framesize.Width, framesize.Height));
// add frame
var bpp = 4;
var buffersize = bpp * framesize.Width * framesize.Height;
var span = new Span<byte>(bitmap.GetPixels().ToPointer(), buffersize);
var img = new ImageData(span, ImagePixelFormat.Bgra32, framesize);
outfile.Video.AddFrame(img);
}
}
}
}

Просмотреть файл

@ -0,0 +1,34 @@
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using FluidSharp.State;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FluidSharp.Views.Android
{
public abstract class AndroidFluidWidgetView : RelativeLayout
{
public abstract SKSize PlatformScale { get; }
public abstract VisualState VisualState { get; }
protected AndroidFluidWidgetView(Context context) : base(context)
{
}
public void AddOnMainThread(View childview)
{
((Activity)Context).RunOnUiThread(() => AddView(childview));
}
}
}

Просмотреть файл

@ -51,6 +51,9 @@
<Link>FluidWidgetView.cs</Link>
</Compile>
<Compile Include="FluidUIActivity.cs" />
<Compile Include="FluidWidgetCanvasView.cs" />
<Compile Include="FluidWidgetGLView.cs" />
<Compile Include="AndroidFluidWidgetView.cs" />
<Compile Include="NativeViewManager.cs" />
<Compile Include="NativeViews\FontExtensions.cs" />
<Compile Include="NativeViews\KeyboardExtensions.cs" />
@ -58,6 +61,7 @@
<Compile Include="NativeViews\NativeViewExtensions.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SkiaViews.cs" />
<Compile Include="SkiaView.cs" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -2,7 +2,7 @@
<package >
<metadata>
<id>FluidSharp.Views.Android</id>
<version>1.0.4-alpha</version>
<version>1.0.5-alpha</version>
<title>$title$</title>
<authors>Wouter Steenbergen</authors>
<owners>My Daily Bits LLC</owners>

Просмотреть файл

@ -10,7 +10,7 @@ namespace FluidSharp.Views.Android
{
public readonly IWidgetSource WidgetSource;
private FluidWidgetView FluidWidgetView;
private AndroidFluidWidgetView FluidWidgetView;
//private KeyboardTracker KeyboardTracker;
//private nfloat OriginalInsetBottom;

Просмотреть файл

@ -0,0 +1,225 @@
using FluidSharp.Engine;
using FluidSharp.Interop;
using FluidSharp.State;
using FluidSharp.Widgets;
using FluidSharp.Widgets.Native;
using SkiaSharp;
using System;
#if __FORMS__
using FluidSharp.Views.Forms.NativeViews;
namespace FluidSharp.Views.Forms
#elif __WINDOWSFORMS__
using FluidSharp.Views.WindowsForms.NativeViews;
using System.Windows.Forms;
namespace FluidSharp.Views.WindowsForms
#elif __ANDROID__
using FluidSharp.Views.Android.NativeViews;
using Android.App;
using Android.Views;
namespace FluidSharp.Views.Android
#elif __IOS__
using UIKit;
using FluidSharp.Views.iOS.NativeViews;
namespace FluidSharp.Views.iOS
#elif __UWP__
namespace FluidSharp.Views.UWP
#endif
{
#if __ANDROID__
public class FluidWidgetCanvasView : AndroidFluidWidgetView, IFluidWidgetView
#else
public class FluidWidgetCanvasView : SkiaCanvasView, IFluidWidgetView
#endif
{
/// <summary>
/// The source of widgets, either overwrite MakeWidget, or set the WidgetSource to implement a custom view
/// </summary>
public IWidgetSource WidgetSource { get => widgetSource; set { widgetSource = value; SkiaView.InvalidatePaint(); } }
private IWidgetSource widgetSource;
public Device Device;
private FluidWidgetViewImplementation implementation;
public FluidWidgetViewImplementation Implementation
{
get => implementation;
set
{
if (implementation != null) implementation.Dispose();
implementation = value;
}
}
public NativeViewManager NativeViewManager;
public override VisualState VisualState => Implementation.VisualState;
/// <summary>
/// Set AutoSizeHeight to true if the view should be sized by the (painted) height of the widgets.
/// The default is false.
/// </summary>
public bool AutoSizeHeight { get; set; }
private float LastPaintWidth = -1;
private float LastHeightRequest = -1;
#if __ANDROID__
private SkiaCanvasView SkiaView;
public override SKSize PlatformScale => SkiaView.PlatformScale;
public FluidWidgetCanvasView(global::Android.Content.Context context) : base(context)
{
SkiaView = new SkiaCanvasView(context);
var fillparams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
AddView(SkiaView, fillparams);
#else
private SkiaCanvasView SkiaView => this;
public FluidWidgetCanvasView()
{
#endif
Device = new Device();
#if __FORMS__
Device.FlowDirection = Xamarin.Forms.Device.FlowDirection == Xamarin.Forms.FlowDirection.RightToLeft ? SkiaSharp.TextBlocks.Enum.FlowDirection.RightToLeft : SkiaSharp.TextBlocks.Enum.FlowDirection.LeftToRight;
#endif
NativeViewManager = new NativeViewManager(this);
Implementation = new FluidWidgetViewImplementation(SkiaView, this, Device);
RegisterNativeViews();
}
#if __ANDROID__
public FluidWidgetCanvasView(global::Android.Content.Context context, bool CreatesOwnImplementation) : base(context)
#else
protected FluidWidgetCanvasView(bool CreatesOwnImplementation)
#endif
{
if (!CreatesOwnImplementation) throw new ArgumentOutOfRangeException(nameof(CreatesOwnImplementation));
}
protected void RegisterNativeViews()
{
#if __IOS__
NativeViewManager.RegisterNativeView<NativeTextboxWidget, UIView>(
(w, c) => ((INativeTextboxImpl)c).Context.Equals(w.Context),
(w) => w.Keyboard == Keyboard.MultiLine ? (UIView)
new NativeMultiLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context } :
new NativeSingleLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
#else
NativeViewManager.RegisterNativeView<NativeTextboxWidget, NativeTextboxImpl>(
(w, c) => c.Context.Equals(w.Context),
#if __ANDROID__
(w) => new NativeTextboxImpl(Context, VisualState.RequestRedraw) { Context = w.Context }
#else
(w) => new NativeTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
#endif
#endif
);
}
public INativeViewManager GetNativeViewManager() => NativeViewManager;
/// <summary>
/// Either overwrite this method or set WidgetSource to implement a custom view
/// </summary>
public virtual Widget MakeWidget(VisualState visualState)
{
if (WidgetSource == null)
return Rectangle.Fill(SKColors.Teal);
else
return WidgetSource.MakeWidget(visualState);
}
public void SetHeight(float height)
{
// Maximum height: screen / device size
#if __FORMS__
#elif __WINDOWSFORMS__
height = Math.Min(Height, Screen.FromControl(this).WorkingArea.Height);
#elif __ANDROID__
#elif __IOS__
#elif __UWP__
#endif
// Minimum height: 10
height = Math.Max(height, 10);
// Apply height
#if __FORMS__
if (this.Height < height - 5 || this.Height > height + 5) InvalidateMeasure();
#elif __WINDOWSFORMS__
this.Height = (int)height;
#elif __ANDROID__
#elif __IOS__
#elif __UWP__
#endif
}
public virtual SKColor GetBackgroundColor(VisualState visualState)
{
if (widgetSource is IBackgroundColorSource backgroundColorSource) return backgroundColorSource.GetBackgroundColor(visualState);
return default;
}
#if __FORMS__
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
LastPaintWidth = (float)Width;
}
protected override void InvalidateMeasure()
{
base.InvalidateMeasure();
InvalidatePaint();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext == null)
Implementation.Dispose();
}
protected override Xamarin.Forms.SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (Width == LastPaintWidth && LastHeightRequest > -1)
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(LastPaintWidth, LastHeightRequest));
var request = Implementation.Measure(new SKSize((float)widthConstraint, (float)heightConstraint));
System.Diagnostics.Debug.WriteLine($"LinkTileView Measured: {request} ({widthConstraint}, {heightConstraint}) ");
if (float.IsInfinity(request.Width) || float.IsInfinity(request.Height))
return base.OnMeasure(widthConstraint, heightConstraint);
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(request.Width, request.Height));
}
#endif
#if __WINDOWSFORMS__
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
Implementation.Dispose();
}
#endif
}
}

Просмотреть файл

@ -0,0 +1,225 @@
using FluidSharp.Engine;
using FluidSharp.Interop;
using FluidSharp.State;
using FluidSharp.Widgets;
using FluidSharp.Widgets.Native;
using SkiaSharp;
using System;
#if __FORMS__
using FluidSharp.Views.Forms.NativeViews;
namespace FluidSharp.Views.Forms
#elif __WINDOWSFORMS__
using FluidSharp.Views.WindowsForms.NativeViews;
using System.Windows.Forms;
namespace FluidSharp.Views.WindowsForms
#elif __ANDROID__
using FluidSharp.Views.Android.NativeViews;
using Android.App;
using Android.Views;
namespace FluidSharp.Views.Android
#elif __IOS__
using UIKit;
using FluidSharp.Views.iOS.NativeViews;
namespace FluidSharp.Views.iOS
#elif __UWP__
namespace FluidSharp.Views.UWP
#endif
{
#if __ANDROID__
public class FluidWidgetGLView : AndroidFluidWidgetView, IFluidWidgetView
#else
public class FluidWidgetGLView : SkiaGLView, IFluidWidgetView
#endif
{
/// <summary>
/// The source of widgets, either overwrite MakeWidget, or set the WidgetSource to implement a custom view
/// </summary>
public IWidgetSource WidgetSource { get => widgetSource; set { widgetSource = value; SkiaView.InvalidatePaint(); } }
private IWidgetSource widgetSource;
public Device Device;
private FluidWidgetViewImplementation implementation;
public FluidWidgetViewImplementation Implementation
{
get => implementation;
set
{
if (implementation != null) implementation.Dispose();
implementation = value;
}
}
public NativeViewManager NativeViewManager;
public override VisualState VisualState => Implementation.VisualState;
/// <summary>
/// Set AutoSizeHeight to true if the view should be sized by the (painted) height of the widgets.
/// The default is false.
/// </summary>
public bool AutoSizeHeight { get; set; }
private float LastPaintWidth = -1;
private float LastHeightRequest = -1;
#if __ANDROID__
private SkiaGLView SkiaView;
public override SKSize PlatformScale => SkiaView.PlatformScale;
public FluidWidgetGLView(global::Android.Content.Context context) : base(context)
{
SkiaView = new SkiaGLView(context);
var fillparams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
AddView(SkiaView, fillparams);
#else
private SkiaGLView SkiaView => this;
public FluidWidgetGLView()
{
#endif
Device = new Device();
#if __FORMS__
Device.FlowDirection = Xamarin.Forms.Device.FlowDirection == Xamarin.Forms.FlowDirection.RightToLeft ? SkiaSharp.TextBlocks.Enum.FlowDirection.RightToLeft : SkiaSharp.TextBlocks.Enum.FlowDirection.LeftToRight;
#endif
NativeViewManager = new NativeViewManager(this);
Implementation = new FluidWidgetViewImplementation(SkiaView, this, Device);
RegisterNativeViews();
}
#if __ANDROID__
public FluidWidgetGLView(global::Android.Content.Context context, bool CreatesOwnImplementation) : base(context)
#else
protected FluidWidgetGLView(bool CreatesOwnImplementation)
#endif
{
if (!CreatesOwnImplementation) throw new ArgumentOutOfRangeException(nameof(CreatesOwnImplementation));
}
protected void RegisterNativeViews()
{
#if __IOS__
NativeViewManager.RegisterNativeView<NativeTextboxWidget, UIView>(
(w, c) => ((INativeTextboxImpl)c).Context.Equals(w.Context),
(w) => w.Keyboard == Keyboard.MultiLine ? (UIView)
new NativeMultiLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context } :
new NativeSingleLineTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
#else
NativeViewManager.RegisterNativeView<NativeTextboxWidget, NativeTextboxImpl>(
(w, c) => c.Context.Equals(w.Context),
#if __ANDROID__
(w) => new NativeTextboxImpl(Context, VisualState.RequestRedraw) { Context = w.Context }
#else
(w) => new NativeTextboxImpl(VisualState.RequestRedraw) { Context = w.Context }
#endif
#endif
);
}
public INativeViewManager GetNativeViewManager() => NativeViewManager;
/// <summary>
/// Either overwrite this method or set WidgetSource to implement a custom view
/// </summary>
public virtual Widget MakeWidget(VisualState visualState)
{
if (WidgetSource == null)
return Rectangle.Fill(SKColors.Teal);
else
return WidgetSource.MakeWidget(visualState);
}
public void SetHeight(float height)
{
// Maximum height: screen / device size
#if __FORMS__
#elif __WINDOWSFORMS__
height = Math.Min(Height, Screen.FromControl(this).WorkingArea.Height);
#elif __ANDROID__
#elif __IOS__
#elif __UWP__
#endif
// Minimum height: 10
height = Math.Max(height, 10);
// Apply height
#if __FORMS__
if (this.Height < height - 5 || this.Height > height + 5) InvalidateMeasure();
#elif __WINDOWSFORMS__
this.Height = (int)height;
#elif __ANDROID__
#elif __IOS__
#elif __UWP__
#endif
}
public virtual SKColor GetBackgroundColor(VisualState visualState)
{
if (widgetSource is IBackgroundColorSource backgroundColorSource) return backgroundColorSource.GetBackgroundColor(visualState);
return default;
}
#if __FORMS__
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
LastPaintWidth = (float)Width;
}
protected override void InvalidateMeasure()
{
base.InvalidateMeasure();
InvalidatePaint();
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext == null)
Implementation.Dispose();
}
protected override Xamarin.Forms.SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
if (Width == LastPaintWidth && LastHeightRequest > -1)
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(LastPaintWidth, LastHeightRequest));
var request = Implementation.Measure(new SKSize((float)widthConstraint, (float)heightConstraint));
System.Diagnostics.Debug.WriteLine($"LinkTileView Measured: {request} ({widthConstraint}, {heightConstraint}) ");
if (float.IsInfinity(request.Width) || float.IsInfinity(request.Height))
return base.OnMeasure(widthConstraint, heightConstraint);
return new Xamarin.Forms.SizeRequest(new Xamarin.Forms.Size(request.Width, request.Height));
}
#endif
#if __WINDOWSFORMS__
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
Implementation.Dispose();
}
#endif
}
}

Просмотреть файл

@ -21,9 +21,9 @@ namespace FluidSharp.Views.Android
{
// public View View;
public FluidWidgetView ViewGroup;
public AndroidFluidWidgetView ViewGroup;
public NativeViewManager(FluidWidgetView viewGroup)
public NativeViewManager(AndroidFluidWidgetView viewGroup)
{
ViewGroup = viewGroup;
}
@ -34,7 +34,7 @@ namespace FluidSharp.Views.Android
for (int i = 0; i < ViewGroup.ChildCount; i++)
{
var child = ViewGroup.GetChildAt(i);
if (!(child is SkiaView))
if (!(child is SkiaGLView || child is SkiaCanvasView))
yield return child;
}
}
@ -65,7 +65,7 @@ namespace FluidSharp.Views.Android
nativeImpl.SetBounds(targetbounds);
nativeImpl.UpdateControl(nativeViewWidget, rect, original);
}
}
}
}

Просмотреть файл

@ -1,4 +1,5 @@
#define USEGL
#if false
#define USEGL
using Android.App;
using Android.Views;
using FluidSharp.Touch;
@ -277,3 +278,4 @@ namespace FluidSharp.Views.Android
}
}
#endif

Просмотреть файл

@ -0,0 +1,507 @@
using Android.App;
using Android.Views;
using FluidSharp.Touch;
using SkiaSharp;
using SkiaSharp.Views.Android;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AView = Android.Views.View;
namespace FluidSharp.Views.Android
{
public class SkiaGLView : SKGLTextureView, ISkiaView
{
float ISkiaView.Width => Width / PlatformScale.Width;
float ISkiaView.Height => Height / PlatformScale.Height;
public SKSize PlatformScale;
SKSize GetSize() => new SKSize(Width / PlatformScale.Width, Height / PlatformScale.Height);
SKPoint ScalePoint(SKPoint point) => new SKPoint(point.X / PlatformScale.Width, point.Y / PlatformScale.Height);
public event EventHandler<PaintSurfaceEventArgs> PaintViewSurface;
public new event EventHandler<TouchActionEventArgs> Touch;
public void InvalidatePaint()
{
((Activity)Context).RunOnUiThread(() => Invalidate());
}
public SkiaGLView(global::Android.Content.Context context) : base(context)
{
PlatformScale = new SKSize(Resources.DisplayMetrics.Xdpi / 140, Resources.DisplayMetrics.Ydpi / 140);
}
protected override void OnPaintSurface(SKPaintGLSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
// Make sure the canvas is drawn using pixel coordinates (but still high res):
// var factor = (float)MathF.Round(e.BackendRenderTarget.Width / w * 4) / 4;
var platformzoom = SKMatrix.CreateScale(PlatformScale.Width, PlatformScale.Height);
//var platformzoom = SKMatrix.CreateScale(factor, factor);
canvas.Concat(ref platformzoom);
var w = ((ISkiaView)this).Width;
var h = ((ISkiaView)this).Height;
PaintViewSurface?.Invoke(this, new PaintSurfaceEventArgs(canvas, w, h, e.Surface, default));
}
private void Touchrecognizer_Touch(object sender, TouchActionEventArgs e)
{
//System.Diagnostics.Debug.WriteLine("touch");
Touch?.Invoke(this, e);
}
//protected override void Dispose(bool disposing)
//{
// // detach all events before disposing
// var controller = (ISKCanvasViewController)Element;
// if (controller != null)
// {
// controller.SurfaceInvalidated -= OnSurfaceInvalidated;
// controller.GetCanvasSize -= OnGetCanvasSize;
// }
// var control = Control;
// if (control != null)
// {
// control.PaintSurface -= OnPaintSurface;
// }
// // detach, regardless of state
// touchHandler.Detach(control);
// base.Dispose(disposing);
//}
bool capture;
int[] twoIntArray = new int[2];
const bool Capture = true;
public override bool OnTouchEvent(MotionEvent e)
{
// return base.OnTouchEvent(e);
//}
//private void OnTouch(object sender, TouchEventArgs args)
//{
// // Two object common to all the events
// var senderView = sender as global::AView;
// MotionEvent motionEvent = args.Event;
var senderView = this;
var motionEvent = e;
// Get the pointer index
int pointerIndex = motionEvent.ActionIndex;
// Get the id that identifies a finger over the course of its progress
int id = motionEvent.GetPointerId(pointerIndex);
senderView.GetLocationOnScreen(twoIntArray);
var pointinview = ScalePoint(new SKPoint(motionEvent.GetX(pointerIndex), motionEvent.GetY(pointerIndex)));
var pointondevice = ScalePoint(new SKPoint(twoIntArray[0] + pointinview.X,
twoIntArray[1] + pointinview.Y));
// Use ActionMasked here rather than Action to reduce the number of possibilities
switch (motionEvent.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
FireEvent(id, TouchActionType.Pressed, pointondevice, pointinview, true);
capture = Capture;
break;
case MotionEventActions.Move:
// Multiple Move events are bundled, so handle them in a loop
for (pointerIndex = 0; pointerIndex < motionEvent.PointerCount; pointerIndex++)
{
id = motionEvent.GetPointerId(pointerIndex);
if (capture)
{
senderView.GetLocationOnScreen(twoIntArray);
pointinview = ScalePoint(new SKPoint(motionEvent.GetX(pointerIndex), motionEvent.GetY(pointerIndex)));
pointondevice = ScalePoint(new SKPoint(twoIntArray[0] + pointinview.X,
twoIntArray[1] + pointinview.Y));
FireEvent(id, TouchActionType.Moved, pointondevice, pointinview, true);
}
else
{
//CheckForBoundaryHop(id, pointondevice);
FireEvent(id, TouchActionType.Moved, pointondevice, pointinview, true);
}
}
break;
case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
if (capture)
{
FireEvent(id, TouchActionType.Released, pointondevice, pointinview, false);
}
else
{
//CheckForBoundaryHop(id, pointondevice);
FireEvent(id, TouchActionType.Released, pointondevice, pointinview, false);
}
break;
case MotionEventActions.Cancel:
if (capture)
{
FireEvent(id, TouchActionType.Cancelled, pointondevice, pointinview, false);
}
else
{
FireEvent(id, TouchActionType.Cancelled, pointondevice, pointinview, false);
}
break;
}
void FireEvent(long id, TouchActionType actionType, SKPoint pointondevice, SKPoint pointinview, bool isInContact)
{
//var rootview = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
//var ondevice = touch.LocationInView(rootview);
//var pointondevice = new SKPoint((float)ondevice.X, (float)ondevice.Y);
// Convert touch location to Xamarin.Forms Point value
//var cgPoint = touch.LocationInView(recognizer.View);
//var xfPoint = new SKPoint((float)cgPoint.X, (float)cgPoint.Y);
//var viewsize = recognizer.View.Bounds.Size;
// var cgsize = touch.LocationInView(recognizer.View);
//var size = new SKSize(Width, Height);
var size = GetSize();
System.Diagnostics.Debug.WriteLine($"Touch {actionType} {pointondevice}");
Touch?.Invoke(this, new TouchActionEventArgs(id, actionType, pointondevice, pointinview, size, isInContact));
}
return true;
}
//void CheckForBoundaryHop(int id, Point pointerLocation)
//{
// TouchEffect touchEffectHit = null;
// foreach (AView 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;
// }
//}
//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));
//}
}
public class SkiaCanvasView : SKCanvasView, ISkiaView
{
float ISkiaView.Width => Width / PlatformScale.Width;
float ISkiaView.Height => Height / PlatformScale.Height;
public SKSize PlatformScale;
SKSize GetSize() => new SKSize(Width / PlatformScale.Width, Height / PlatformScale.Height);
SKPoint ScalePoint(SKPoint point) => new SKPoint(point.X / PlatformScale.Width, point.Y / PlatformScale.Height);
public event EventHandler<PaintSurfaceEventArgs> PaintViewSurface;
public new event EventHandler<TouchActionEventArgs> Touch;
public void InvalidatePaint()
{
((Activity)Context).RunOnUiThread(() => Invalidate());
}
public SkiaCanvasView(global::Android.Content.Context context) : base(context)
{
PlatformScale = new SKSize(Resources.DisplayMetrics.Xdpi / 140, Resources.DisplayMetrics.Ydpi / 140);
this.PaintSurface += SkiaControl_PaintSurface;
}
private void SkiaControl_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
// Make sure the canvas is drawn using pixel coordinates (but still high res):
// var factor = (float)MathF.Round(e.Info.Width / w * 4) / 4;
var platformzoom = SKMatrix.CreateScale(PlatformScale.Width, PlatformScale.Height);
//var platformzoom = SKMatrix.CreateScale(factor, factor);
canvas.Concat(ref platformzoom);
var w = ((ISkiaView)this).Width;
var h = ((ISkiaView)this).Height;
PaintViewSurface?.Invoke(this, new PaintSurfaceEventArgs(canvas, w, h, e.Surface, default));
}
private void Touchrecognizer_Touch(object sender, TouchActionEventArgs e)
{
//System.Diagnostics.Debug.WriteLine("touch");
Touch?.Invoke(this, e);
}
//protected override void Dispose(bool disposing)
//{
// // detach all events before disposing
// var controller = (ISKCanvasViewController)Element;
// if (controller != null)
// {
// controller.SurfaceInvalidated -= OnSurfaceInvalidated;
// controller.GetCanvasSize -= OnGetCanvasSize;
// }
// var control = Control;
// if (control != null)
// {
// control.PaintSurface -= OnPaintSurface;
// }
// // detach, regardless of state
// touchHandler.Detach(control);
// base.Dispose(disposing);
//}
bool capture;
int[] twoIntArray = new int[2];
const bool Capture = true;
public override bool OnTouchEvent(MotionEvent e)
{
// return base.OnTouchEvent(e);
//}
//private void OnTouch(object sender, TouchEventArgs args)
//{
// // Two object common to all the events
// var senderView = sender as global::AView;
// MotionEvent motionEvent = args.Event;
var senderView = this;
var motionEvent = e;
// Get the pointer index
int pointerIndex = motionEvent.ActionIndex;
// Get the id that identifies a finger over the course of its progress
int id = motionEvent.GetPointerId(pointerIndex);
senderView.GetLocationOnScreen(twoIntArray);
var pointinview = ScalePoint(new SKPoint(motionEvent.GetX(pointerIndex), motionEvent.GetY(pointerIndex)));
var pointondevice = ScalePoint(new SKPoint(twoIntArray[0] + pointinview.X,
twoIntArray[1] + pointinview.Y));
// Use ActionMasked here rather than Action to reduce the number of possibilities
switch (motionEvent.ActionMasked)
{
case MotionEventActions.Down:
case MotionEventActions.PointerDown:
FireEvent(id, TouchActionType.Pressed, pointondevice, pointinview, true);
capture = Capture;
break;
case MotionEventActions.Move:
// Multiple Move events are bundled, so handle them in a loop
for (pointerIndex = 0; pointerIndex < motionEvent.PointerCount; pointerIndex++)
{
id = motionEvent.GetPointerId(pointerIndex);
if (capture)
{
senderView.GetLocationOnScreen(twoIntArray);
pointinview = ScalePoint(new SKPoint(motionEvent.GetX(pointerIndex), motionEvent.GetY(pointerIndex)));
pointondevice = ScalePoint(new SKPoint(twoIntArray[0] + pointinview.X,
twoIntArray[1] + pointinview.Y));
FireEvent(id, TouchActionType.Moved, pointondevice, pointinview, true);
}
else
{
//CheckForBoundaryHop(id, pointondevice);
FireEvent(id, TouchActionType.Moved, pointondevice, pointinview, true);
}
}
break;
case MotionEventActions.Up:
case MotionEventActions.Pointer1Up:
if (capture)
{
FireEvent(id, TouchActionType.Released, pointondevice, pointinview, false);
}
else
{
//CheckForBoundaryHop(id, pointondevice);
FireEvent(id, TouchActionType.Released, pointondevice, pointinview, false);
}
break;
case MotionEventActions.Cancel:
if (capture)
{
FireEvent(id, TouchActionType.Cancelled, pointondevice, pointinview, false);
}
else
{
FireEvent(id, TouchActionType.Cancelled, pointondevice, pointinview, false);
}
break;
}
void FireEvent(long id, TouchActionType actionType, SKPoint pointondevice, SKPoint pointinview, bool isInContact)
{
//var rootview = UIApplication.SharedApplication.KeyWindow.RootViewController.View;
//var ondevice = touch.LocationInView(rootview);
//var pointondevice = new SKPoint((float)ondevice.X, (float)ondevice.Y);
// Convert touch location to Xamarin.Forms Point value
//var cgPoint = touch.LocationInView(recognizer.View);
//var xfPoint = new SKPoint((float)cgPoint.X, (float)cgPoint.Y);
//var viewsize = recognizer.View.Bounds.Size;
// var cgsize = touch.LocationInView(recognizer.View);
//var size = new SKSize(Width, Height);
var size = GetSize();
System.Diagnostics.Debug.WriteLine($"Touch {actionType} {pointondevice}");
Touch?.Invoke(this, new TouchActionEventArgs(id, actionType, pointondevice, pointinview, size, isInContact));
}
return true;
}
//void CheckForBoundaryHop(int id, Point pointerLocation)
//{
// TouchEffect touchEffectHit = null;
// foreach (AView 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;
// }
//}
//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));
//}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
#if !__IOS__
#if !__IOS__ && !__ANDROID__
using FluidSharp.Engine;
using FluidSharp.Interop;