简体   繁体   中英

how to enable scrolling when zoom in and zoom out on a content page in xamarin forms

i am trying to zoom in and zoom out on a content page using xamarin.forms. I am able zoom in and zoom out but the problem is scrolling is not working. i want zoom an image. with this code zooming is working perfectly. But while zooming i am not able to see full image. i must scroll to view the rest of the image. for that i need to scroll. but scrolling is not working.

XAML

xmlns:helper="clr-namespace:KPGTC.Deals.Mobile.Helpers"
<helper:PinchToZoomContainer>
    <helper:PinchToZoomContainer.Content>
        <Image x:Name="img_Popup"/>
    </helper:PinchToZoomContainer.Content>
</helper:PinchToZoomContainer>

Code:

public class PinchToZoomContainer : ContentView
{
    double MIN_SCALE = 1;
    double MAX_SCALE = 4;
    double startScale = 1;
    double currentScale = 1;
    double xOffset = 0;
    double yOffset = 0;
    bool _isActive = false;

    public PinchToZoomContainer()
    {
        DependencyService.Get<IHelpers>().ShowAlert("Double-tap to zoom");

        //var _pinchGesture = new PinchGestureRecognizer();
        //_pinchGesture.PinchUpdated += OnPinchUpdated;
        //GestureRecognizers.Add(_pinchGesture);

        var _tapGesture = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
        _tapGesture.Tapped += On_Tapped;
        GestureRecognizers.Add(_tapGesture);

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

        TranslationX = 0;
        TranslationY = 0;
        _isActive = false;
    }

    private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
    {
        if (_isActive)
        {
            if (e.TotalX > 0)
            {
                if (e.TotalX > 2)
                {
                    TranslationX += 15;
                }
            }
            else
            {
                if (e.TotalX < -2)
                {
                    TranslationX -= 15;
                }
            }
        }
    }

    private void On_Tapped(object sender, EventArgs e)
    {
        if (Scale > MIN_SCALE)
        {
            _isActive = false;
            this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
            this.TranslateTo(0, 0, 250, Easing.CubicInOut);
        }
        else
        {
            _isActive = true;
            AnchorX = AnchorY = 0.5;
            this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
        }
    }

    void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
    {
        if (e.Status == GestureStatus.Started)
        {
            startScale = Content.Scale;
            Content.AnchorX = 0;
            Content.AnchorY = 0;
        }
        if (e.Status == GestureStatus.Running)
        {
            currentScale += (e.Scale - 1) * startScale;
            currentScale = Math.Max(1, currentScale);

            double renderedX = Content.X + xOffset;
            double deltaX = renderedX / Width;
            double deltaWidth = Width / (Content.Width * startScale);
            double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

            double renderedY = Content.Y + yOffset;
            double deltaY = renderedY / Height;
            double deltaHeight = Height / (Content.Height * startScale);
            double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

            double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
            double 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;
        }
        if (e.Status == GestureStatus.Completed)
        {
            xOffset = Content.TranslationX;
            yOffset = Content.TranslationY;
        }
    }
}

Alright, this was quite a tough one and admittedly I don't understand fully how I made it, but I made it.

Some thoughts:

  • You mixed the translation of the container and the content, which is quite tricky to handle - if this is possible at all
  • When panning, you added 15 every time the pan event was raised, but there is a better way: Just store the initial offset of the content and then add the TotalX and TotalY respectively to the TranslationX and the TranslationY of the content (this was the easy part)
  • Panning while zooming was quite hard to get right and I had to find it out by trial and error
    • Basically you have to store the origin of the pinch gesture when the gesture starts and calculate the diff between the original origin and the current origin
    • Then you have to add the diff (multiplied by the with and height respectively of the control) to the target translation

Here is the code for the panning:

private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
    if (e.StatusType == GestureStatus.Started)
    {
        this.xOffset = this.Content.TranslationX;
        this.yOffset = this.Content.TranslationY;
    }

    if (e.StatusType != GestureStatus.Completed
        && e.StatusType != GestureStatus.Canceled)
    {
        this.Content.TranslationX = this.xOffset + e.TotalX;
        this.Content.TranslationY = this.yOffset + e.TotalY;
    }

    if (e.StatusType == GestureStatus.Completed)
    {
        this.xOffset = this.Content.TranslationX;
        this.yOffset = this.Content.TranslationY;
    }
}

And here for the pinching

void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
    if (e.Status == GestureStatus.Started)
    {
        this.startScale = this.Content.Scale;
        this.Content.AnchorX = 0;
        this.Content.AnchorY = 0;

        this.startScaleOrigin = e.ScaleOrigin;
    }

    if (e.Status == GestureStatus.Running)
    {
        var originDiff = PinchToZoomContainer.CalculateDiff(e.ScaleOrigin, this.startScaleOrigin);

        this.currentScale += (e.Scale - 1) * this.startScale;
        this.currentScale = Math.Max(1, this.currentScale);

        double renderedX = this.Content.X + this.xOffset;
        double deltaX = renderedX / this.Width;
        double deltaWidth = this.Width / (this.Content.Width * this.startScale);
        double originX = (this.startScaleOrigin.X - deltaX) * deltaWidth;

        double renderedY = this.Content.Y + this.yOffset;
        double deltaY = renderedY / this.Height;
        double deltaHeight = this.Height / (this.Content.Height * this.startScale);
        double originY = (startScaleOrigin.Y - deltaY) * deltaHeight;

        double targetX = this.xOffset - ((originX) * this.Content.Width) * (this.currentScale - this.startScale) - originDiff.X * this.Content.Width;
        double targetY = this.yOffset - ((originY) * this.Content.Height) * (this.currentScale - this.startScale) - originDiff.Y * this.Content.Height;

        this.Content.TranslationX = targetX.Clamp(-this.Content.Width * (this.currentScale - 1), 0);
        this.Content.TranslationY = targetY.Clamp(-this.Content.Height * (this.currentScale - 1), 0);

        this.Content.Scale = this.currentScale;
    }

    if (e.Status == GestureStatus.Completed)
    {
        this.xOffset = this.Content.TranslationX;
        this.yOffset = this.Content.TranslationY;
    }
}

(Of course you have to add Point startScaleOrigin to your class).

Finally you need the method to calculate the distance between two points

private static Point CalculateDiff(Point first, Point second)
{
    return second.Offset(-first.X, -first.Y);
}

Unfortunately I did not manage to get the tapping right, but I think you should be able to figure it out from here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM