Xamarin形成捏和平底锅

3 pinchzoom xamarin xamarin.forms

我已经单独实现了pan和pinch,它工作正常.我现在正在尝试使用捏和平移,我看到一些问题.这是我的代码:

XAML:

<AbsoluteLayout x:Name="PinchZoomContainer">
  <controls:NavBar x:Name="NavBar" ShowPrevNext="true" ShowMenu="false" IsModal="true" />
  <controls:PanContainer  x:Name="PinchToZoomContainer">
    <Image x:Name="ImageMain" />
  </controls:PanContainer>
</AbsoluteLayout>
Run Code Online (Sandbox Code Playgroud)

捏/平移手势添加:

var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);

var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
Run Code Online (Sandbox Code Playgroud)

平移方法:

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:
            // Translate and ensure we don't pan beyond the wrapped user interface element bounds.
            Content.TranslationX = Math.Max(Math.Min(0, x + e.TotalX), -Math.Abs(Content.Width - App.ScreenWidth));
            Content.TranslationY = Math.Max(Math.Min(0, y + e.TotalY), -Math.Abs(Content.Height - App.ScreenHeight));
            break;

        case GestureStatus.Completed:
            // Store the translation applied during the pan
            x = Content.TranslationX;
            y = Content.TranslationY;
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

捏法:

void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
    if (e.Status == GestureStatus.Started)
    {
        // Store the current scale factor applied to the wrapped user interface element,
        // and zero the components for the center point of the translate transform.
        startScale = Content.Scale;
        //ImageMain.AnchorX = 0;
        //ImageMain.AnchorY = 0;
    }
    if (e.Status == GestureStatus.Running)
    {
        // Calculate the scale factor to be applied.
        currentScale += (e.Scale - 1) * startScale;
        currentScale = Math.Max(1, currentScale);
        currentScale = Math.Min(currentScale, 2.5);
        // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
        // so get the X pixel coordinate.
        double renderedX = Content.X + xOffset;
        double deltaX = renderedX / Width;
        double deltaWidth = Width / (Content.Width * startScale);
        double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

        // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
        // so get the Y pixel coordinate.
        double renderedY = Content.Y + yOffset;
        double deltaY = renderedY / Height;
        double deltaHeight = Height / (Content.Height * startScale);
        double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

        // Calculate the transformed element pixel coordinates.
        double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
        double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);

        // Apply translation based on the change in origin.
        Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
        Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);

        // Apply scale factor
        Content.Scale = currentScale;
    }
    if (e.Status == GestureStatus.Completed)
    {
        // Store the translation delta's of the wrapped user interface element.
        xOffset = Content.TranslationX;
        yOffset = Content.TranslationY;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我关闭任一手势并仅使用另一手势,那么功能完美无缺.当我添加平移和捏合手势时会出现问题.似乎正在发生的事情是这样的:

1)平底锅实际上似乎按预期工作2)当你最初平移图像时,让我们说,将图像移动到Y中心和X中心,然后你尝试缩放,图像被设置回它的初始状态.然后,当您平移时,它会将您移回到您尝试缩放之前的位置(这就是为什么我说平底锅工作正常).

从我对调试的理解是,当你缩放时,它没有考虑你当前所处的位置.因此,当您先平移,然后进行缩放时,它不会放大您所处的位置,而是放大图像的起始点.然后当你尝试从那里平移时,pan方法仍会记住你的位置,它会让你回到你试图缩放之前的位置.

希望对此有所了解.显然,我的捏合方法存在问题.我只是想(显然无法弄清楚)我需要在其中添加逻辑,考虑到你当前所处的位置.

Gáb*_*bor 9

主要原因可能是每个人似乎都复制并使用此代码(来自dev.xamarin网站),其中包含非常复杂且非常不必要的坐标计算:-).这是不必要的,因为我们可以简单地让视图为我们做繁重的工作,使用正是这个目的的AnchorXAnchorY属性.

我们可以进行双击操作以放大并恢复到原始比例.请注意,因为Xamarin无法为其Tap事件提供坐标值(实际上是一个非常不明智的决定),我们现在只能从中心放大:

private void OnTapped(object sender, EventArgs e) 
{
    if (Scale > MIN_SCALE) 
    {
        this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
        this.TranslateTo(0, 0, 250, Easing.CubicInOut);
    }
    else 
    {
        AnchorX = AnchorY = 0.5;
        this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
    }
}
Run Code Online (Sandbox Code Playgroud)

夹点处理程序同样简单,根本不需要计算任何翻译.我们所要做的就是将锚点设置为捏起点,框架将完成其余的工作,缩放将围绕这一点进行.请注意,我们在这里甚至还有一个额外的功能,弹性反弹缩放比例两端的过冲.

private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e) 
{
    switch (e.Status) 
    {
        case GestureStatus.Started:
            StartScale = Scale;
            AnchorX = e.ScaleOrigin.X;
            AnchorY = e.ScaleOrigin.Y;
            break;

        case GestureStatus.Running:
            double current = Scale + (e.Scale - 1) * StartScale;
            Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
            break;

        case GestureStatus.Completed:
            if (Scale > MAX_SCALE)
                this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
            else if (Scale < MIN_SCALE)
                this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

和平移处理程序,甚至更简单.一开始,我们计算锚点的起点,在平移过程中,我们不断更改锚点.这个锚点相对于视图区域,我们可以很容易地将它夹在0和1之间,这样就可以在极端情况下停止平移而无需任何平移计算.

private void OnPanUpdated(object sender, PanUpdatedEventArgs e) 
{
    switch (e.StatusType) 
    {
        case GestureStatus.Started:
            StartX = (1 - AnchorX) * Width;
            StartY = (1 - AnchorY) * Height;
            break;

        case GestureStatus.Running:
            AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
            AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用的常量和变量就是这些:

private const double MIN_SCALE = 1;
private const double MAX_SCALE = 8;
private const double OVERSHOOT = 0.15;
private double StartX, StartY;
private double StartScale;
Run Code Online (Sandbox Code Playgroud)


小智 3

采用完全不同的方法来处理这个问题。对于任何遇到问题的人来说,这都是 100% 有效的。

OnPan更新

void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
    var s = (ContentView)sender;

    // do not allow pan if the image is in its intial size
    if (currentScale == 1)
        return;

    switch (e.StatusType)
    {
        case GestureStatus.Running:
            double xTrans = xOffset + e.TotalX, yTrans = yOffset + e.TotalY;
            // do not allow verical scorlling unless the image size is bigger than the screen
            s.Content.TranslateTo(xTrans, yTrans, 0, Easing.Linear);
            break;

        case GestureStatus.Completed:
            // Store the translation applied during the pan
            xOffset = s.Content.TranslationX;
            yOffset = s.Content.TranslationY;

            // center the image if the width of the image is smaller than the screen width
            if (originalWidth * currentScale < ScreenWidth && ScreenWidth > ScreenHeight)
                xOffset = (ScreenWidth - originalWidth * currentScale) / 2 - s.Content.X;
            else
                xOffset = System.Math.Max(System.Math.Min(0, xOffset), -System.Math.Abs(originalWidth * currentScale - ScreenWidth));

            // center the image if the height of the image is smaller than the screen height
            if (originalHeight * currentScale < ScreenHeight && ScreenHeight > ScreenWidth)
                yOffset = (ScreenHeight - originalHeight * currentScale) / 2 - s.Content.Y;
            else
                //yOffset = System.Math.Max(System.Math.Min((originalHeight - (ScreenHeight)) / 2, yOffset), -System.Math.Abs((originalHeight * currentScale - ScreenHeight - (originalHeight - ScreenHeight) / 2)) + (NavBar.Height + App.StatusBarHeight));
                yOffset = System.Math.Max(System.Math.Min((originalHeight - (ScreenHeight)) / 2, yOffset), -System.Math.Abs((originalHeight * currentScale - ScreenHeight - (originalHeight - ScreenHeight) / 2)));

            // bounce the image back to inside the bounds
            s.Content.TranslateTo(xOffset, yOffset, 500, Easing.BounceOut);
            break;
    }
}
Run Code Online (Sandbox Code Playgroud)

捏合更新

void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
    var s = (ContentView)sender;

    if (e.Status == GestureStatus.Started)
    {
        // Store the current scale factor applied to the wrapped user interface element,
        // and zero the components for the center point of the translate transform.
        startScale = s.Content.Scale;

        s.Content.AnchorX = 0;
        s.Content.AnchorY = 0;
    }
    if (e.Status == GestureStatus.Running)
    {

        // Calculate the scale factor to be applied.
        currentScale += (e.Scale - 1) * startScale;
        currentScale = System.Math.Max(1, currentScale);
        currentScale = System.Math.Min(currentScale, 5);

        //scaleLabel.Text = "Scale: " + currentScale.ToString ();

        // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
        // so get the X pixel coordinate.
        double renderedX = s.Content.X + xOffset;
        double deltaX = renderedX / App.ScreenWidth;
        double deltaWidth = App.ScreenWidth / (s.Content.Width * startScale);
        double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

        // The ScaleOrigin is in relative coordinates to the wrapped user interface element,
        // so get the Y pixel coordinate.
        double renderedY = s.Content.Y + yOffset;

        double deltaY = renderedY / App.ScreenHeight;
        double deltaHeight = App.ScreenHeight / (s.Content.Height * startScale);
        double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

        // Calculate the transformed element pixel coordinates.
        double targetX = xOffset - (originX * s.Content.Width) * (currentScale - startScale);
        double targetY = yOffset - (originY * s.Content.Height) * (currentScale - startScale);

        // Apply translation based on the change in origin.
        var transX = targetX.Clamp(-s.Content.Width * (currentScale - 1), 0);
        var transY = targetY.Clamp(-s.Content.Height * (currentScale - 1), 0);


        s.Content.TranslateTo(transX, transY, 0, Easing.Linear);
        // Apply scale factor.
        s.Content.Scale = currentScale;
    }
    if (e.Status == GestureStatus.Completed)
    {
        // Store the translation applied during the pan
        xOffset = s.Content.TranslationX;
        yOffset = s.Content.TranslationY;

        // center the image if the width of the image is smaller than the screen width
        if (originalWidth * currentScale < ScreenWidth && ScreenWidth > ScreenHeight)
            xOffset = (ScreenWidth - originalWidth * currentScale) / 2 - s.Content.X;
        else
            xOffset = System.Math.Max(System.Math.Min(0, xOffset), -System.Math.Abs(originalWidth * currentScale - ScreenWidth));

        // center the image if the height of the image is smaller than the screen height
        if (originalHeight * currentScale < ScreenHeight && ScreenHeight > ScreenWidth)
            yOffset = (ScreenHeight - originalHeight * currentScale) / 2 - s.Content.Y;
        else
            yOffset = System.Math.Max(System.Math.Min((originalHeight - ScreenHeight) / 2, yOffset), -System.Math.Abs(originalHeight * currentScale - ScreenHeight - (originalHeight - ScreenHeight) / 2));

        // bounce the image back to inside the bounds
        s.Content.TranslateTo(xOffset, yOffset, 500, Easing.BounceOut);
    }
}
Run Code Online (Sandbox Code Playgroud)

OnSizeAllocation(其中大部分您可能不需要,但有些您需要。考虑ScreenWidth, ScreenHeight, yOffset, xOffset, currentScale

protected override void OnSizeAllocated(double width, double height)
{            
    base.OnSizeAllocated(width, height); //must be called

    if (width != -1 &&  (ScreenWidth != width || ScreenHeight != height))
    {
        ResetLayout(width, height);

        originalWidth = initialLoad ?
            ImageWidth >= 960 ?
               App.ScreenWidth > 320 
                    ? 768 
                    : 320 
                :  ImageWidth / 3
            : imageContainer.Content.Width / imageContainer.Content.Scale;

        var normalizedHeight = ImageWidth >= 960 ?
                App.ScreenWidth > 320 ? ImageHeight / (ImageWidth / 768) 
                : ImageHeight / (ImageWidth / 320) 
            : ImageHeight / 3;

        originalHeight = initialLoad ? 
            normalizedHeight : (imageContainer.Content.Height / imageContainer.Content.Scale);

        ScreenWidth = width;
        ScreenHeight = height;

        xOffset = imageContainer.TranslationX;
        yOffset = imageContainer.TranslationY;

        currentScale = imageContainer.Scale;

        if (initialLoad)
            initialLoad = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

布局(C# 中的 XAML)

ImageMain = new Image
{
    HorizontalOptions = LayoutOptions.CenterAndExpand,
    VerticalOptions = LayoutOptions.CenterAndExpand,
    Aspect = Aspect.AspectFill,
    Source = ImageMainSource
};

imageContainer = new ContentView
{
    Content = ImageMain,
    BackgroundColor = Xamarin.Forms.Color.Black,
    WidthRequest = App.ScreenWidth - 250
};

var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
imageContainer.GestureRecognizers.Add(panGesture);

var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
imageContainer.GestureRecognizers.Add(pinchGesture);

double smallImageHeight = ImageHeight / (ImageWidth / 320);

absoluteLayout = new AbsoluteLayout
{
    HeightRequest = App.ScreenHeight,
    BackgroundColor = Xamarin.Forms.Color.Black,
};

AbsoluteLayout.SetLayoutFlags(imageContainer, AbsoluteLayoutFlags.All);
AbsoluteLayout.SetLayoutBounds(imageContainer, new Rectangle(0f, 0f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
absoluteLayout.Children.Add(imageContainer, new Rectangle(0, 0, 1, 1), AbsoluteLayoutFlags.All);
Content = absoluteLayout;
Run Code Online (Sandbox Code Playgroud)

  • 似乎缺少一些相关代码。例如,ImageWidth 和 ImageHeight 以及 ResetLayout() 函数都是未定义的。相关的 xaml 也可能有助于避免对 imageContainer 是什么做出假设。 (2认同)