On Gesture

When building a mobile app in FMX, I had an issue getting pinch-to-zoom working.

A common approach to mobile development in FMX is to use a single form with multiple views implemented as frames. These views can be docked in and out as required, and the approach works very well.

However, no matter what I tried, I couldn't get pinch-to-zoom working. In the end, the solution was simple and should have been obvious to me. I had to hook into the actual Form's OnGesture event.


Hooking into the Form

When a view is docked, I register the gesture handler:

procedure TPictureView.Docked(const aFileName, aTitle: string);
begin
    // preserve the main form configuration
    fOnGesture := Form.OnGesture;
    fGestures  := Form.Touch.InteractiveGestures;

    // take control of zoom
    Form.Touch.InteractiveGestures := 
        Form.Touch.InteractiveGestures + [TInteractiveGesture.Zoom];

    Form.OnGesture := OnFormGesture;
end;

When the view is undocked, the gesture handler is unregistered:

procedure TPictureView.Undocked;
begin
    // restore the main form configuration
    Form.OnGesture := fOnGesture;
    Form.Touch.InteractiveGestures := fGestures;
end;

Pinch and Zoom Handler

Next, the code in the view's OnFormGesture method handles the pinch-to-zoom gesture.

We intercept the zoom gesture and delegate the remaining gestures to the form's original handler:

procedure TPictureView.OnFormGesture(
    Sender: TObject; 
    const EventInfo: TGestureEventInfo; 
    var Handled: Boolean);
begin
    // handle pinch-to-zoom
    if EventInfo.GestureID = igiZoom then
    begin
        if (not (TInteractiveGestureFlag.gfBegin in EventInfo.Flags)) and
           (not (TInteractiveGestureFlag.gfEnd   in EventInfo.Flags)) then
        begin
            var img    := imgPicture;
            var center := img.Position.Point + PointF(img.Width / 2, img.Height / 2);

            img.Width  := Max(img.Width +  (EventInfo.Distance - fLastDistance), 10);
            img.Height := Max(img.Height + (EventInfo.Distance - fLastDistance), 10);

            img.Position.X := center.X - img.Width  / 2;
            img.Position.Y := center.Y - img.Height / 2;            
        end;
        
        // remember the distance for the next zoom event
        fLastDistance := EventInfo.Distance;
        
        Handled := true;
        exit;
    end;

    // delegate other gestures to the form's original handler
    if Assigned(fOnGesture) then
        fOnGesture(Sender, EventInfo, Handled);
end;

Sample Project

Delphi ships with a sample project called ImageZoom:

# Delphi 13.1 Samples folder
C:\Users\Public\Documents\Embarcadero\Studio\37.0\Samples\

# The ImageZoom example can be found in this subdirectory            
Object Pascal\Mobile Snippets\InteractiveGestures\ImageZoom