Xamarin ZoomIn lags on Android

2019-09-16 02:04发布

问题:

I have found some code to zoom In and zoom Out for Xamarin Forms. On iOS it's working smoothly but on Android it has some lags, here is video how it working http://fs2.fex.net/get/688885800398/30602583/5998218f/2017_08_07_19_45_28.mp4 I do not have any idea how to make it smoothly, could you help with this ?

 public class ZoomImage : Image
    {
        private const double MIN_SCALE = 1;
        private const double MAX_SCALE = 1.5;
        private const double OVERSHOOT = 0.9;
        private double StartScale;
        private double LastX, LastY;

        public ZoomImage()
        {
            var pinch = new PinchGestureRecognizer();
            pinch.PinchUpdated += OnPinchUpdated;
            GestureRecognizers.Add(pinch);

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

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

            Scale = MIN_SCALE;
            TranslationX = TranslationY = 0;
            AnchorX = AnchorY = 0;
        }

        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
        {
            Scale = MIN_SCALE;
            TranslationX = TranslationY = 0;
            AnchorX = AnchorY = 0;
            return base.OnMeasure(widthConstraint  - 50, heightConstraint);
        }

        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; //TODO tapped position
                this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
            }
        }

        private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
        {
            switch (e.StatusType)
            {
                case GestureStatus.Started:
                    LastX = (1 - AnchorX) * Width;
                    LastY = (1 - AnchorY) * Height;
                    break;
                case GestureStatus.Running:
                    AnchorX = Clamp(1 - (LastX + e.TotalX) / Width, 0, 1);
                    AnchorY = Clamp(1 - (LastY + e.TotalY) / Height, 0, 1);
                    break;
            }
        }

        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, 50, Easing.SpringOut);
                    else if (Scale < MIN_SCALE)
                        this.ScaleTo(MIN_SCALE, 50, Easing.SpringOut);
                    break;
            }
        }

        private T Clamp<T>(T value, T minimum, T maximum) where T : IComparable
        {
            if (value.CompareTo(minimum) < 0)
                return minimum;
            else if (value.CompareTo(maximum) > 0)
                return maximum;
            else
                return value;
        }
    }

回答1:

I do not have any idea how to make it smoothly, could you help with this

In order to zoom in/out a view, a pinch gesture is enough. Don't write scale logic in your Pan and Tapped gesture events. And also, I don't understand what is the OVERSHOOT for. but Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT)) also leads to the unexpected behavior of the zoom in/out.

So the fixed ZoomImage should looks like this(I commented out the unnecessary codes):

public class ZoomImage : Image
{
    private const double MIN_SCALE = 1;
    private const double MAX_SCALE = 1.5;
    private const double OVERSHOOT = 0.9;
    private double StartScale;
    private double LastX, LastY;

    public ZoomImage()
    {
        var pinch = new PinchGestureRecognizer();
        pinch.PinchUpdated += OnPinchUpdated;
        GestureRecognizers.Add(pinch);

        //don't register the Pan gesture and Tap gesture for zoom in/out

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

        //var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
        //tap.Tapped += OnTapped;
        //GestureRecognizers.Add(tap);

        Scale = MIN_SCALE;
        TranslationX = TranslationY = 0;
        AnchorX = AnchorY = 0;
    }

    protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
    {
        Scale = MIN_SCALE;
        TranslationX = TranslationY = 0;
        AnchorX = AnchorY = 0;
        return base.OnMeasure(widthConstraint - 50, heightConstraint);
    }


    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));
                var parent = (StackLayout)this.Parent;
                var child = parent.Children[1];
                if (child is Label)
                {
                    (child as Label).Text = Clamp(current, MIN_SCALE, MAX_SCALE).ToString();
                }
                Scale = Clamp(current, MIN_SCALE, MAX_SCALE);
                break;
            case GestureStatus.Completed:
                //Scale is already limited to Min_SCALE and MAX_SCALE, the following codes is not necessary

                //if (Scale > MAX_SCALE)
                //this.ScaleTo(MAX_SCALE, 50, Easing.SpringOut);
                //else if (Scale < MIN_SCALE)
                //this.ScaleTo(MIN_SCALE, 50, Easing.SpringOut);
                    break;
        }
    }

    private T Clamp<T>(T value, T minimum, T maximum) where T : IComparable
    {
        if (value.CompareTo(minimum) < 0)
            return minimum;
        else if (value.CompareTo(maximum) > 0)
            return maximum;
        else
            return value;
    }
}