Xamarin - 双指缩放在 IOS 中不起作用。 Android 工作正常
Xamarin - Pinch to zoom not working in IOS. working fine on Android
我已经使用了 SebasianKruse https://forums.xamarin.com/discussion/74168/full-screen-image-viewer-with-pinch-to-zoom-pan-to-move-tap-to-show-captions-for-xamarin-forms/p2 在这里发布的代码来能够缩放和平移我的图像。它在 Android 中运行良好,但在 IOS 中我无法缩放和平移。
我试图在 OnPanUpdated
中设置一个断点,但它从未到达 IOS。
这是我的代码:
xaml:
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1">
<Service:PinchToZoomContainer HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ffimageloading:CachedImage RetryCount="5" RetryDelay="1" CacheDuration="1" x:Name="MyImage" HorizontalOptions="Fill" VerticalOptions="CenterAndExpand" DownsampleToViewSize="False">
</ffimageloading:CachedImage>
</Service:PinchToZoomContainer>
</StackLayout>
<StackLayout BindingContext="{x:Reference MyImage}" IsVisible="{Binding IsLoading}" Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
<ActivityIndicator BindingContext="{x:Reference MyImage}" IsRunning="{Binding IsLoading}" />
<Label Text="Loading Hi-Res Image..." BindingContext="{x:Reference MyImage}" IsVisible="{Binding IsLoading}" HorizontalOptions="Center" TextColor="Black"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
Service/PinchToZoomContainer.cs
using System;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
namespace GalShare.Service
{
public class PinchToZoomContainer : ContentView
{
private double _startScale, _currentScale;
private double _startX, _startY;
private double _xOffset, _yOffset;
public double MinScale { get; set; } = 1;
public double MaxScale { get; set; } = 4;
public PinchToZoomContainer()
{
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
tap.Tapped += OnTapped;
GestureRecognizers.Add(tap);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
var pan = new PanGestureRecognizer();
pan.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(pan);
}
protected override void OnSizeAllocated(double width, double height)
{
RestoreScaleValues();
Content.AnchorX = 0.5;
Content.AnchorY = 0.5;
base.OnSizeAllocated(width, height);
}
private void RestoreScaleValues()
{
Content.ScaleTo(MinScale, 250, Easing.CubicInOut);
Content.TranslateTo(0, 0, 250, Easing.CubicInOut);
_currentScale = MinScale;
_xOffset = Content.TranslationX = 0;
_yOffset = Content.TranslationY = 0;
}
private void OnTapped(object sender, EventArgs e)
{
if (Content.Scale > MinScale)
{
RestoreScaleValues();
}
else
{
//todo: Add tap position somehow
StartScaling();
ExecuteScaling(MaxScale, .5, .5);
EndGesture();
}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
StartScaling();
break;
case GestureStatus.Running:
ExecuteScaling(e.Scale, e.ScaleOrigin.X, e.ScaleOrigin.Y);
break;
case GestureStatus.Completed:
EndGesture();
break;
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
_startX = e.TotalX;
_startY = e.TotalY;
Content.AnchorX = 0;
Content.AnchorY = 0;
break;
case GestureStatus.Running:
var maxTranslationX = Content.Scale * Content.Width - Content.Width;
Content.TranslationX = Math.Min(0, Math.Max(-maxTranslationX, _xOffset + e.TotalX - _startX));
var maxTranslationY = Content.Scale * Content.Height - Content.Height;
Content.TranslationY = Math.Min(0, Math.Max(-maxTranslationY, _yOffset + e.TotalY - _startY));
break;
case GestureStatus.Completed:
EndGesture();
break;
}
}
private void StartScaling()
{
_startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
private void ExecuteScaling(double scale, double x, double y)
{
_currentScale += (scale - 1) * _startScale;
_currentScale = Math.Max(MinScale, _currentScale);
_currentScale = Math.Min(MaxScale, _currentScale);
var deltaX = (Content.X + _xOffset) / Width;
var deltaWidth = Width / (Content.Width * _startScale);
var originX = (x - deltaX) * deltaWidth;
var deltaY = (Content.Y + _yOffset) / Height;
var deltaHeight = Height / (Content.Height * _startScale);
var originY = (y - deltaY) * deltaHeight;
var targetX = _xOffset - (originX * Content.Width) * (_currentScale - _startScale);
var targetY = _yOffset - (originY * Content.Height) * (_currentScale - _startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (_currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (_currentScale - 1), 0);
Content.Scale = _currentScale;
}
private void EndGesture()
{
_xOffset = Content.TranslationX;
_yOffset = Content.TranslationY;
}
}
}
为什么此代码在 IOS 上不起作用?根据上面论坛中其他用户的帖子,它应该可以在两个系统上工作..
在真实物理设备上测试不会有任何差异事件。
这是因为Xcode11.4引入了一个新的协议成员
UIGestureRecognizerDelegate 和我们最初建议的默认值
ShouldReceiveEvent 与世界的关系不佳。
我们的产品团队已在 Xamarin.iOS 13.16.0.13 中修复此问题:
https://github.com/xamarin/Xamarin.Forms/issues/10162#issuecomment-607585466
您可以在 Mac 上手动下载并安装 pkg。但是 Windows 上的 VS 已经发布了新版本,现在我们只能在安装新的 Xamarin iOS 版本后暂时使用 VS for Mac 进行开发。
我已经使用了 SebasianKruse https://forums.xamarin.com/discussion/74168/full-screen-image-viewer-with-pinch-to-zoom-pan-to-move-tap-to-show-captions-for-xamarin-forms/p2 在这里发布的代码来能够缩放和平移我的图像。它在 Android 中运行良好,但在 IOS 中我无法缩放和平移。
我试图在 OnPanUpdated
中设置一个断点,但它从未到达 IOS。
这是我的代码:
xaml:
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1">
<Service:PinchToZoomContainer HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ffimageloading:CachedImage RetryCount="5" RetryDelay="1" CacheDuration="1" x:Name="MyImage" HorizontalOptions="Fill" VerticalOptions="CenterAndExpand" DownsampleToViewSize="False">
</ffimageloading:CachedImage>
</Service:PinchToZoomContainer>
</StackLayout>
<StackLayout BindingContext="{x:Reference MyImage}" IsVisible="{Binding IsLoading}" Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
<ActivityIndicator BindingContext="{x:Reference MyImage}" IsRunning="{Binding IsLoading}" />
<Label Text="Loading Hi-Res Image..." BindingContext="{x:Reference MyImage}" IsVisible="{Binding IsLoading}" HorizontalOptions="Center" TextColor="Black"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
Service/PinchToZoomContainer.cs
using System;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
namespace GalShare.Service
{
public class PinchToZoomContainer : ContentView
{
private double _startScale, _currentScale;
private double _startX, _startY;
private double _xOffset, _yOffset;
public double MinScale { get; set; } = 1;
public double MaxScale { get; set; } = 4;
public PinchToZoomContainer()
{
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
tap.Tapped += OnTapped;
GestureRecognizers.Add(tap);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
var pan = new PanGestureRecognizer();
pan.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(pan);
}
protected override void OnSizeAllocated(double width, double height)
{
RestoreScaleValues();
Content.AnchorX = 0.5;
Content.AnchorY = 0.5;
base.OnSizeAllocated(width, height);
}
private void RestoreScaleValues()
{
Content.ScaleTo(MinScale, 250, Easing.CubicInOut);
Content.TranslateTo(0, 0, 250, Easing.CubicInOut);
_currentScale = MinScale;
_xOffset = Content.TranslationX = 0;
_yOffset = Content.TranslationY = 0;
}
private void OnTapped(object sender, EventArgs e)
{
if (Content.Scale > MinScale)
{
RestoreScaleValues();
}
else
{
//todo: Add tap position somehow
StartScaling();
ExecuteScaling(MaxScale, .5, .5);
EndGesture();
}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
StartScaling();
break;
case GestureStatus.Running:
ExecuteScaling(e.Scale, e.ScaleOrigin.X, e.ScaleOrigin.Y);
break;
case GestureStatus.Completed:
EndGesture();
break;
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
_startX = e.TotalX;
_startY = e.TotalY;
Content.AnchorX = 0;
Content.AnchorY = 0;
break;
case GestureStatus.Running:
var maxTranslationX = Content.Scale * Content.Width - Content.Width;
Content.TranslationX = Math.Min(0, Math.Max(-maxTranslationX, _xOffset + e.TotalX - _startX));
var maxTranslationY = Content.Scale * Content.Height - Content.Height;
Content.TranslationY = Math.Min(0, Math.Max(-maxTranslationY, _yOffset + e.TotalY - _startY));
break;
case GestureStatus.Completed:
EndGesture();
break;
}
}
private void StartScaling()
{
_startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
private void ExecuteScaling(double scale, double x, double y)
{
_currentScale += (scale - 1) * _startScale;
_currentScale = Math.Max(MinScale, _currentScale);
_currentScale = Math.Min(MaxScale, _currentScale);
var deltaX = (Content.X + _xOffset) / Width;
var deltaWidth = Width / (Content.Width * _startScale);
var originX = (x - deltaX) * deltaWidth;
var deltaY = (Content.Y + _yOffset) / Height;
var deltaHeight = Height / (Content.Height * _startScale);
var originY = (y - deltaY) * deltaHeight;
var targetX = _xOffset - (originX * Content.Width) * (_currentScale - _startScale);
var targetY = _yOffset - (originY * Content.Height) * (_currentScale - _startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (_currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (_currentScale - 1), 0);
Content.Scale = _currentScale;
}
private void EndGesture()
{
_xOffset = Content.TranslationX;
_yOffset = Content.TranslationY;
}
}
}
为什么此代码在 IOS 上不起作用?根据上面论坛中其他用户的帖子,它应该可以在两个系统上工作..
在真实物理设备上测试不会有任何差异事件。
这是因为Xcode11.4引入了一个新的协议成员 UIGestureRecognizerDelegate 和我们最初建议的默认值 ShouldReceiveEvent 与世界的关系不佳。
我们的产品团队已在 Xamarin.iOS 13.16.0.13 中修复此问题: https://github.com/xamarin/Xamarin.Forms/issues/10162#issuecomment-607585466 您可以在 Mac 上手动下载并安装 pkg。但是 Windows 上的 VS 已经发布了新版本,现在我们只能在安装新的 Xamarin iOS 版本后暂时使用 VS for Mac 进行开发。