Jump to content
Hans♫

FMX on mobile: Best practice to handle touch scroll of components?

Recommended Posts

Having various components on a TListbox, scrolling does not work when i touch a component that has HitTest=True.
What is the best way to solve this?

 

I have comboboxes, radiobuttons, checkboxes, etc on Listboxitems to create a dynamic interface. So far I have only used native components on iOS (TMS iCL) where scrolling vs control interaction, is handled automatically by the OS in a very smooth way. Now we are preparing an Android version based on our FMX view, but on FMX it does not seem to work out of the box. If HitTest is true for a component, then scrolling does not work, and if HitTest is false for a component, then it does not respond, but scrolling works.

 

I found this nice solution made by David Nottage, but it requires to create a descending class of each component used:
https://stackoverflow.com/questions/57452568/prevent-firing-events-while-scrolling-tvertscrollbox

Share this post


Link to post

You can use OnClick normally with this code:

uses
  FMX.Objects, FMX.Math, FMX.Math.Vectors;

type
  TipControl = class(TRectangle)
  private const
    DEFAULT_MOVEMENT_TOLERANCE = 4;
  private
    FAbsolutePressedPoint: TPointF;
    FIsMouseDown: Boolean;
    FIsValidClick: Boolean;
    FLastMouseActivePoint: TPointF;
    FMovementTolerance: Single;
    function IsMovementToleranceStored: Boolean;
  protected
    function CheckHasValidClick: Boolean; virtual;
    procedure Click; override;
    procedure DoClick; virtual;
    procedure MouseDown(AButton: TMouseButton; AShift: TShiftState; X, Y: Single); override;
    procedure MouseMove(AShift: TShiftState; X, Y: Single); override;
    procedure MouseUp(AButton: TMouseButton; AShift: TShiftState; X, Y: Single); override;
  public
    constructor Create(AOwner: TComponent); override;
    property IsValidClick: Boolean read FIsValidClick;
  published
    property MovementTolerance: Single read FMovementTolerance write FMovementTolerance stored IsMovementToleranceStored;
  end;

function TipControl.CheckHasValidClick: Boolean;

  function CheckIsMouseOver: Boolean;
  begin
    Result := FIsMouseOver and TouchTargetExpansion.MarginRect(LocalRect).Contains(FLastMouseActivePoint);
  end;

begin
  Result := FIsValidClick and FIsMouseDown and CheckIsMouseOver and
    ((FMovementTolerance = 0) or ((PressedPosition.Distance(FLastMouseActivePoint) <= FMovementTolerance) and
    (FAbsolutePressedPoint.Distance(LocalToAbsolute(FLastMouseActivePoint)) <= FMovementTolerance)));
end;

procedure TipControl.Click;
begin
  if Pressed then
  begin
    if not CheckHasValidClick then
      FIsValidClick := False;
    if IsValidClick then
      DoClick;
  end
  else
    DoClick;
end;

constructor TipControl.Create(AOwner: TComponent);
begin
  inherited;
  FMovementTolerance := DEFAULT_MOVEMENT_TOLERANCE;
  FLastMouseActivePoint := PointF(-10000,-10000);
end;

procedure TipControl.DoClick;
begin
  inherited Click;
end;

function TipControl.IsMovementToleranceStored: Boolean;
begin
  Result := not SameValue(FMovementTolerance, DEFAULT_MOVEMENT_TOLERANCE, TEpsilon.Position);
end;

procedure TipControl.MouseDown(AButton: TMouseButton; AShift: TShiftState; X,
  Y: Single);
begin
  FAbsolutePressedPoint := LocalToAbsolute(PointF(X, Y));
  FIsValidClick := True;
  FIsMouseDown := True;
  FLastMouseActivePoint := PointF(X, Y);
  inherited;
end;

procedure TipControl.MouseMove(AShift: TShiftState; X, Y: Single);
begin
  if CheckHasValidClick then
  begin
    FLastMouseActivePoint := PointF(X, Y);
    if not CheckHasValidClick then
      FIsValidClick := False;
  end
  else
    FLastMouseActivePoint := PointF(X, Y);
  inherited;
end;

procedure TipControl.MouseUp(AButton: TMouseButton; AShift: TShiftState; X,
  Y: Single);
var
  LInvalidPressed: Boolean;
begin
  FLastMouseActivePoint := PointF(X, Y);
  LInvalidPressed := not CheckHasValidClick;
  if LInvalidPressed then
    FIsValidClick := False;
  FIsMouseDown := False;
  inherited;
end;

 

Edited by vfbb
  • Like 1

Share this post


Link to post
32 minutes ago, vfbb said:

You can try something like this:

Thank you, but it seems to be a solution similar to the one I refer to? I prefer not to have a custom edition of each components placed on the Listbox.

 

However, I also realize that both solutions relies on that scrolling actually does work when tapping a control, but it does not work for me. As soon as Hittest=true for the control on the listbox, then it does not scroll at all when I touch that control.

 

 

Share this post


Link to post

This is strange because the scrollbox does not use mousedown, mouseup, it uses gestures, and with hittest or without hit test in the components above, it should work in the same way.

Share this post


Link to post
19 hours ago, vfbb said:

This is strange because the scrollbox does not use mousedown, mouseup, it uses gestures, and with hittest or without hit test in the components above, it should work in the same way.

Thank you for your reply. If thats true, then there must be something in my setup that causes the default behavior not to work. I wonder what that is?


My parentship hierachy looks like this:
MainForm>TLayout>TRectangle>TRectangle>TListBox>TListBoxItem>TRadioButton

 

When I set HitTest=true for the RadioButton, then touch scrolling of the listbox only works if I touch outside the Radiobutton. If I touch the radiobutton then it selects the radiobutton, but scrolling does not work.
I have no Gesture manager assigned and no InteractiveGestures selected.

Share this post


Link to post

@Hans♫ I couldn't simulate your problem. I set up the same scenario you described in a blank project and the scroll worked perfectly. I also mounted on a TVertScrollBox and it worked perfectly. I'm using RAD Studio 11, but it works the same as in previous versions. Try simulating on a blank project to see where the problem is. I don't know what's going on, but maybe some other control is capturing the gesture...

Share this post


Link to post

I found the reason for the problem: Delphi handles touch events by ownership and not by parentship. Scrolling only works for scrollboxes OWNED by the mainform. It is not enough that that the mainform is their parent.

 

In my situation my ownership looks like this:

  MainForm>TLayout (the layout animates the showing of other "windows")

In another unit I have:

  MyFrame>TRectangle>TRectangle>TListBox

 

Now when I want to show the content of the Frame, I Create the frame with the Application as the owner, and set the parent of the root Rectangle to the TLayout of the mainform, to slide it in.

With this structure, scrolling does NOT work.

 

However, if I create MyFrame with the MainForm as the owner, then it works!

Share this post


Link to post
51 minutes ago, Hans♫ said:

However, if I create MyFrame with the MainForm as the owner, then it works! 

Thanks for the insights.

I'm wondering if this might cause any problem with memory leaks or AV, when creating/destroying frames at runtime, in changing, random order.

If the Application->MainForm is used over several units, inside specific components only, this always leaves a bad taste.

 

This leads me to the question what is the right, intended, official way to use the Application->MainForm and the AOwner of Frames/Components.

Shall it be in a strict tree-structure, or does it not matter, to mix tree and direct owenerships as you like ?

Does it matter if the relations were mixed by designtime and runtime ?

Does FMX behaves differently than VCL in that regard ?

 

Thanks god, the termination sequence on mobile is not that critical, since this is killed by phone mostly :classic_smile:

Edited by Rollo62

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×